FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

GitHub Viewer

/* Copyright (C) 2003-2015 LiveCode Ltd. This file is part of LiveCode. LiveCode is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License v3 as published by the Free Software Foundation. LiveCode is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with LiveCode. If not see . */ #include #include #include #include "foundation-private.h" #include "foundation-bidi.h" #include "foundation-chunk.h" #ifdef __LINUX__ #include #include #include #include #endif #ifdef __WINDOWS__ #include #endif //////////////////////////////////////////////////////////////////////////////// // This method ensures there is 'count' chars of empty space starting at 'at' // in 'string'. It returns false if allocation fails. static bool __MCStringExpandAt(MCStringRef string, uindex_t at, uindex_t count); // This method removes 'count' chars from string starting at 'at'. static void __MCStringShrinkAt(MCStringRef string, uindex_t at, uindex_t count); // This method clamps the given range to the valid limits for the string. static void __MCStringClampRange(MCStringRef string, MCRange& x_range); // This method forces a nativization of a string even if there is already a native char ptr. static bool __MCStringNativize(MCStringRef string, uindex_t &r_char_count); static bool __MCStringNativize(MCStringRef string); // This method ensures there is a unichar ptr. static bool __MCStringUnnativize(MCStringRef self); // This method marks the string as changed. static void __MCStringChanged(MCStringRef string, uindex_t simple = kMCStringFlagNoChange, uindex_t combined = kMCStringFlagNoChange, uindex_t native = kMCStringFlagNoChange); // Creates an indirect mutable string with contents. static bool __MCStringCreateIndirect(__MCString *contents, __MCString*& r_string); // Returns true if the string is indirect. static bool __MCStringIsIndirect(__MCString *self); static bool __MCStringMakeImmutable(__MCString *self); // Creates an immutable string from this one, changing 'self' to indirect. static bool __MCStringMakeIndirect(__MCString *self); // Ensures the given mutable but indirect string is direct. static bool __MCStringResolveIndirect(__MCString *self); // Makes direct mutable string indirect, referencing r_new_string. static bool __MCStringCopyMutable(__MCString *self, __MCString*& r_new_string); // Copy the given unicode chars into the target unicode buffer and return true // if all the chars being copied in could be native. static bool __MCStringCopyChars(unichar_t *target, const unichar_t *source, uindex_t count, bool target_can_be_native); static bool MCStringSplitByDelimiterNative(MCStringRef self, MCStringRef p_elem_del, MCStringOptions p_options, MCProperListRef& r_list); // Check the string and set CanBeNative, Basic and Trivial flags accordingly static void __MCStringCheck(MCStringRef self); //////////////////////////////////////////////////////////////////////////////// // AL-2015-02-06: [[ Bug 14504 ]] Add wrappers for string flag and length checking, // for internal use when a string is known to be direct. static bool __MCStringIsNative(MCStringRef self) { MCAssert(!__MCStringIsIndirect(self)); return (self -> flags & kMCStringFlagIsNotNative) == 0; } static bool __MCStringIsChecked(MCStringRef self) { MCAssert(!__MCStringIsIndirect(self)); return (self -> flags & kMCStringFlagIsChecked) != 0; } static bool __MCStringIsTrivial(MCStringRef self) { MCAssert(!__MCStringIsIndirect(self)); __MCStringCheck(self); return (self -> flags & kMCStringFlagIsNotNative) == 0 || (self -> flags & kMCStringFlagIsTrivial) != 0; } static bool __MCStringIsBasic(MCStringRef self) { MCAssert(!__MCStringIsIndirect(self)); __MCStringCheck(self); return (self -> flags & kMCStringFlagIsNotNative) == 0 || (self -> flags & kMCStringFlagIsBasic) != 0; } static bool __MCStringCanBeNative(MCStringRef self) { MCAssert(!__MCStringIsIndirect(self)); return (self -> flags & kMCStringFlagIsNotNative) == 0 || (self -> flags & kMCStringFlagCanBeNative) != 0; } static bool __MCStringCantBeEqualToNative(MCStringRef self, MCStringOptions p_options) { // If self can't be native, then we check the comparison options to see if // it could still be native after normalization. if (!__MCStringCanBeNative(self)) { // At this point self must contain unicode characters which don't directly // map to native. Thus the only way we could be equal to a native string is // if we contain combining sequences which compose to a native char. switch(p_options) { case kMCStringOptionCompareExact: case kMCStringOptionCompareFolded: // If no normalization is taking place, then no composition can occur // so we can't be equal to native. return true; case kMCStringOptionCompareNonliteral: case kMCStringOptionCompareCaseless: // If the string has been checked then we have more information. if (__MCStringIsChecked(self)) { // If there are no combining chars, then normalization is not // going to make a difference - there's no way this string // can be native. if (__MCStringIsTrivial(self)) return true; // If the string is not simple, then even though it contains // combining chars it can't be native. if (!__MCStringIsBasic(self)) return true; } break; default: MCUnreachableReturn(false); } } return false; } static bool __MCStringCopyChars(unichar_t *p_dst, const unichar_t *p_src, uindex_t p_count, bool p_dst_can_be_native) { // If the dst cannot be native, then there's no point checking if the src can be. if (!p_dst_can_be_native) { MCMemoryCopy(p_dst, p_src, p_count * sizeof(unichar_t)); return false; } // Copy the unicode chars to our dst checking if we can nativize as we go. for(uindex_t i = 0; i < p_count; i++) { // If we fail to convert the char to native, then we can't be native so // finish up with a direct copy. char_t t_nchar; if (!MCUnicodeCharMapToNative(p_src[i], t_nchar)) { MCMemoryCopy(p_dst + i, p_src + i, (p_count - i) * sizeof(unichar_t)); return false; } // We still copy across unicode char. p_dst[i] = p_src[i]; } // If we get here, then both src and dst can be native. return true; } static uindex_t __MCStringGetLength(MCStringRef self) { MCAssert(!__MCStringIsIndirect(self)); return self -> char_count; } static bool __MCStringIsEmpty(MCStringRef string) { return string == nil || __MCStringGetLength(string) == 0; } //////////////////////////////////////////////////////////////////////////////// #include "foundation-string-native.cpp.h" //////////////////////////////////////////////////////////////////////////////// // This method creates a 'constant' MCStringRef from the given c-string. At some // point we'll make it work 'magically' at compile/build time. For now, uniquing // and returning that has a similar effect (if slightly slower). MC_DLLEXPORT_DEF MCStringRef MCSTR(const char *p_cstring) { MCAssert(nil != p_cstring); MCStringRef t_string = nullptr; /* UNCHECKED */ MCStringCreateWithNativeChars((const char_t *)p_cstring, strlen(p_cstring), t_string); MCValueRef t_unique_string = nullptr; /* UNCHECKED */ MCValueInter(t_string, t_unique_string); MCValueRelease(t_string); return (MCStringRef)t_unique_string; } //////////////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF bool MCStringCreateWithCString(const char* p_cstring, MCStringRef& r_string) { return MCStringCreateWithNativeChars((const char_t*)p_cstring, p_cstring == nil ? 0 : strlen(p_cstring), r_string); } MC_DLLEXPORT_DEF bool MCStringCreateWithCStringAndRelease(char* p_cstring, MCStringRef& r_string) { MCAssert(nil != p_cstring); if (MCStringCreateWithNativeChars((const char_t *)p_cstring, p_cstring == nil ? 0 : strlen((const char*)p_cstring), r_string)) { delete[] p_cstring; return true; } return false; } MC_DLLEXPORT_DEF const char *MCStringGetCString(MCStringRef p_string) { if (p_string == nil) return nil; __MCAssertIsString(p_string); MCStringNativize(p_string); const char *t_cstring; t_cstring = (const char *)MCStringGetNativeCharPtr(p_string); MCAssert(t_cstring != nil); return t_cstring; } MC_DLLEXPORT_DEF bool MCStringIsEqualToCString(MCStringRef p_string, const char *p_cstring, MCStringOptions p_options) { __MCAssertIsString(p_string); return MCStringIsEqualToNativeChars(p_string, (const char_t *)p_cstring, strlen(p_cstring), p_options); } MC_DLLEXPORT_DEF bool MCStringSubstringIsEqualToCString(MCStringRef p_string, MCRange p_range, const char *p_cstring, MCStringOptions p_options) { __MCAssertIsString(p_string); return MCStringSubstringIsEqualToNativeChars(p_string, p_range, (const char_t *)p_cstring, strlen(p_cstring), p_options); } // Create an immutable string from the given bytes, interpreting them using // the specified encoding. MC_DLLEXPORT_DEF bool MCStringCreateWithBytes(const byte_t *p_bytes, uindex_t p_byte_count, MCStringEncoding p_encoding, bool p_is_external_rep, MCStringRef& r_string) { if (0 == p_byte_count) { if (nil != kMCEmptyString) { r_string = MCValueRetain(kMCEmptyString); return true; } } else { MCAssert(nil != p_bytes); } MCAssert(!p_is_external_rep); switch (p_encoding) { case kMCStringEncodingASCII: case kMCStringEncodingNative: return MCStringCreateWithNativeChars(p_bytes, p_byte_count, r_string); case kMCStringEncodingUTF16: return MCStringCreateWithChars((unichar_t *)p_bytes, p_byte_count / 2, r_string); // AL-2014-31-03: [[ Bug 12067 ]] Fix conversion from little endian bytes. case kMCStringEncodingUTF16LE: case kMCStringEncodingUTF16BE: { unichar_t *t_buffer; uindex_t t_length = p_byte_count / 2; if (!MCMemoryNewArray(t_length, t_buffer)) return false; for (uindex_t i = 0; i < t_length; i++) { if (p_encoding == kMCStringEncodingUTF16BE) t_buffer[i] = (unichar_t)MCSwapInt16BigToHost(((unichar_t *)p_bytes)[i]); else t_buffer[i] = (unichar_t)MCSwapInt16LittleToHost(((unichar_t *)p_bytes)[i]); } return MCStringCreateWithCharsAndRelease(t_buffer, t_length, r_string); } case kMCStringEncodingUTF8: { unichar_t *t_chars; uindex_t t_char_count; t_char_count = MCUnicodeCharsMapFromUTF8(p_bytes, p_byte_count, nil, 0); if (!MCMemoryNewArray(t_char_count, t_chars)) return false; MCUnicodeCharsMapFromUTF8(p_bytes, p_byte_count, t_chars, t_char_count); if (!MCStringCreateWithCharsAndRelease(t_chars, t_char_count, r_string)) { MCMemoryDeleteArray(t_chars); return false; } return true; } case kMCStringEncodingUTF32: case kMCStringEncodingUTF32LE: case kMCStringEncodingUTF32BE: { // Round the byte count to a multiple of UTF-32 units p_byte_count = ((p_byte_count + sizeof(uint32_t) - 1) & ~(sizeof(uint32_t) - 1)); // Convert the string to UTF-16 first. MCAutoArray t_buffer; if (!t_buffer.Extend(p_byte_count / sizeof(uint32_t))) return false; uindex_t t_in_offset; uindex_t t_out_offset = 0; for (t_in_offset = 0; t_in_offset < p_byte_count; t_in_offset += sizeof(uint32_t)) { // BMP characters are output unchanged, non-BMP requires surrogate pairs codepoint_t t_codepoint; t_codepoint = *(uint32_t*)&p_bytes[t_in_offset]; if (p_encoding == kMCStringEncodingUTF32BE) t_codepoint = MCSwapInt32BigToHost(t_codepoint); else if (p_encoding == kMCStringEncodingUTF32LE) t_codepoint = MCSwapInt32LittleToHost(t_codepoint); if (t_codepoint < 0x10000) { t_buffer[t_out_offset] = unichar_t(t_codepoint); t_out_offset += 1; } else { // Split to surrogate pairs // SN-2015-07-03: [[ Bug 15571 ]] Creating a surrogate pair // makes the UTF-16 string longer. if (!t_buffer . Extend(t_buffer . Size() + 1)) return false; unichar_t t_lead, t_trail; t_lead = unichar_t((t_codepoint - 0x10000) >> 10) + 0xD800; t_trail = unichar_t((t_codepoint - 0x10000) & 0x3FF) + 0xDC00; t_buffer[t_out_offset] = t_lead; t_buffer[t_out_offset + 1] = t_trail; t_out_offset += 2; } } return MCStringCreateWithChars(t_buffer.Ptr(), t_out_offset, r_string); } #if !defined(__ISO_8859_1__) case kMCStringEncodingISO8859_1: break; #endif #ifndef __WINDOWS__ case kMCStringEncodingWindows1252: break; #endif #if !defined(__MAC__) && !defined(__IOS__) case kMCStringEncodingMacRoman: break; #endif } return false; } MC_DLLEXPORT_DEF bool MCStringCreateWithBytesAndRelease(byte_t *p_bytes, uindex_t p_byte_count, MCStringEncoding p_encoding, bool p_is_external_rep, MCStringRef& r_string) { if (p_byte_count == 0) { if (kMCEmptyString != nil) { r_string = MCValueRetain(kMCEmptyString); free(p_bytes); return true; } } else { MCAssert(nil != p_bytes); } MCStringRef t_string; t_string = nil; switch (p_encoding) { case kMCStringEncodingASCII: case kMCStringEncodingNative: break; default: if (!MCStringCreateWithBytes(p_bytes, p_byte_count, p_encoding, p_is_external_rep, t_string)) return false; r_string = t_string; free(p_bytes); return true; } bool t_success; t_success = true; if (t_success) t_success = __MCValueCreate(kMCValueTypeCodeString, t_string); if (t_success) t_success = MCMemoryReallocate(p_bytes, p_byte_count + 1, p_bytes); if (t_success) { p_bytes[p_byte_count] = '\0'; t_string -> native_chars = p_bytes; t_string -> char_count = p_byte_count; r_string = t_string; } else MCMemoryDelete(t_string); return t_success; } //////////////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF bool MCStringCreateWithChars(const unichar_t *p_chars, uindex_t p_char_count, MCStringRef& r_string) { if (p_char_count == 0) { if (kMCEmptyString != nil) { r_string = MCValueRetain(kMCEmptyString); return true; } } else { MCAssert(nil != p_chars); } bool t_success; t_success = true; __MCString *self; self = nil; if (t_success) t_success = __MCValueCreate(kMCValueTypeCodeString, self); bool t_not_native; t_not_native = false; if (t_success) t_success = MCMemoryNewArray(p_char_count + 1, self -> native_chars); if (t_success) { uindex_t i; for(i = 0; i < p_char_count; i++) if (!MCUnicodeCharMapToNative(p_chars[i], self -> native_chars[i])) { t_not_native = true; break; } if (t_not_native) { MCMemoryDeleteArray(self -> native_chars); t_success = MCMemoryNewArray(p_char_count + 1, self -> chars); } } if (t_success) { if (t_not_native) { MCStrCharsMapFromUnicode(p_chars, p_char_count, self -> chars, self -> char_count); self -> flags |= kMCStringFlagIsNotNative; } else self -> char_count = p_char_count; r_string = self; } else { if (self != nil) MCMemoryDeleteArray(self -> chars); MCMemoryDelete(self); } return t_success; } MC_DLLEXPORT_DEF bool MCStringCreateWithCharsAndRelease(unichar_t *p_chars, uindex_t p_char_count, MCStringRef& r_string) { MCAssert(nil != p_chars); if (MCStringCreateWithChars(p_chars, p_char_count, r_string)) { free(p_chars); return true; } return false; } MC_DLLEXPORT_DEF bool MCStringCreateUnicodeWithChars(const unichar_t *p_chars, uindex_t p_char_count, MCStringRef& r_string) { if (p_char_count == 0) { if (kMCEmptyString != nil) { r_string = MCValueRetain(kMCEmptyString); return true; } } else { MCAssert(nil != p_chars); } bool t_success; t_success = true; __MCString *self; self = nil; if (t_success) t_success = __MCValueCreate(kMCValueTypeCodeString, self); if (t_success) t_success = MCMemoryNewArray(p_char_count + 1, self -> chars); if (t_success) { MCStrCharsMapFromUnicode(p_chars, p_char_count, self -> chars, self -> char_count); self -> flags |= kMCStringFlagIsNotNative; r_string = self; } else { if (self != nil) MCMemoryDeleteArray(self -> chars); MCMemoryDelete(self); } return t_success; } MC_DLLEXPORT_DEF bool MCStringCreateWithWString(const unichar_t *p_wstring, MCStringRef& r_string) { MCAssert(nil != p_wstring); uindex_t t_length; for(t_length = 0; p_wstring[t_length] != 0; t_length++) ; return MCStringCreateWithChars(p_wstring, t_length, r_string); } MC_DLLEXPORT_DEF bool MCStringCreateWithWStringAndRelease(unichar_t* p_wstring, MCStringRef& r_string) { MCAssert(nil != p_wstring); if (MCStringCreateWithWString(p_wstring, r_string)) { free(p_wstring); return true; } return false; } MC_DLLEXPORT_DEF bool MCStringCreateWithNativeChars(const char_t *p_chars, uindex_t p_char_count, MCStringRef& r_string) { bool t_success; t_success = true; if ((p_chars == nil || p_char_count == 0) && kMCEmptyString != nil) { r_string = MCValueRetain(kMCEmptyString); return true; } MCAssert(nil != p_chars); __MCString *self; self = nil; if (t_success) t_success = __MCValueCreate(kMCValueTypeCodeString, self); if (t_success) t_success = MCMemoryNewArray(p_char_count + 1, self -> native_chars); if (t_success) MCMemoryCopy(self -> native_chars, p_chars, p_char_count); else { if (self != nil) MCMemoryDeleteArray(self -> native_chars); MCMemoryDelete(self); } if (t_success) { self -> char_count = p_char_count; r_string = self; } return t_success; } MC_DLLEXPORT_DEF bool MCStringCreateWithNativeCharsAndRelease(char_t *p_chars, uindex_t p_char_count, MCStringRef& r_string) { return MCStringCreateWithNativeCharBufferAndRelease(p_chars, p_char_count, p_char_count, r_string); } MC_DLLEXPORT_DEF bool MCStringCreateWithNativeCharBufferAndRelease(char_t* p_chars, uindex_t p_char_count, uindex_t p_buffer_length, MCStringRef& r_string) { MCAssert(nil != p_chars); bool t_success; t_success = true; if (p_char_count == 0 && kMCEmptyString != nil) { r_string = MCValueRetain(kMCEmptyString); MCMemoryDeallocate(p_chars); return true; } __MCString *self; self = nil; if (t_success) t_success = __MCValueCreate(kMCValueTypeCodeString, self); uindex_t t_capacity = p_buffer_length; if (t_success && t_capacity < p_char_count + 1) { t_capacity = p_char_count + 1; t_success = MCMemoryReallocate(p_chars, t_capacity, p_chars); } if (t_success) { p_chars[p_char_count] = '\0'; self -> native_chars = p_chars; self -> char_count = p_char_count; self -> capacity = t_capacity; r_string = self; } else MCMemoryDelete(self); return t_success; } static bool MCStringCreateMutableUnicode(uindex_t p_initial_capacity, MCStringRef& r_string) { bool t_success; t_success = true; __MCString *self; self = nil; if (t_success) t_success = __MCValueCreate(kMCValueTypeCodeString, self); if (t_success) { self -> flags |= kMCStringFlagIsNotNative; t_success = __MCStringExpandAt(self, 0, p_initial_capacity); } if (t_success) { self -> flags |= kMCStringFlagIsMutable; self->char_count = 0; r_string = self; } else { MCValueRelease (self); } return t_success; } MC_DLLEXPORT_DEF bool MCStringCreateMutable(uindex_t p_initial_capacity, MCStringRef& r_string) { bool t_success; t_success = true; __MCString *self; self = nil; if (t_success) t_success = __MCValueCreate(kMCValueTypeCodeString, self); if (t_success) t_success = __MCStringExpandAt(self, 0, p_initial_capacity); if (t_success) { self -> flags |= kMCStringFlagIsMutable; self->char_count = 0; r_string = self; } else { MCValueRelease (self); } return t_success; } //////////////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF bool MCStringEncode(MCStringRef p_string, MCStringEncoding p_encoding, bool p_is_external_rep, MCDataRef& r_data) { __MCAssertIsString(p_string); byte_t *t_bytes; uindex_t t_byte_count; if (!MCStringConvertToBytes(p_string, p_encoding, p_is_external_rep, t_bytes, t_byte_count)) return false; if (!MCDataCreateWithBytesAndRelease(t_bytes, t_byte_count, r_data)) { free(t_bytes); return false; } return true; } MC_DLLEXPORT_DEF bool MCStringEncodeAndRelease(MCStringRef p_string, MCStringEncoding p_encoding, bool p_is_external_rep, MCDataRef& r_data) { __MCAssertIsString(p_string); MCDataRef t_data; if (!MCStringEncode(p_string, p_encoding, p_is_external_rep, t_data)) return false; MCValueRelease(p_string); r_data = t_data; return true; } MC_DLLEXPORT_DEF bool MCStringDecode(MCDataRef p_data, MCStringEncoding p_encoding, bool p_is_external_rep, MCStringRef& r_string) { return MCStringCreateWithBytes(MCDataGetBytePtr(p_data), MCDataGetLength(p_data), p_encoding, p_is_external_rep, r_string); } MC_DLLEXPORT_DEF bool MCStringDecodeAndRelease(MCDataRef p_data, MCStringEncoding p_encoding, bool p_is_external_rep, MCStringRef& r_string) { __MCAssertIsData(p_data); MCStringRef t_string; if (!MCStringDecode(p_data, p_encoding, p_is_external_rep, t_string)) return false; MCValueRelease(p_data); r_string = t_string; return true; } // SN-2015-07-27: [[ Bug 15379 ]] This function is only used for internal // purposes, when a LiveCode function parameter can be data - in which case // no char translation must occur between the bytes in the DataRef and the // Unicode string that the engine function takes (in MCR_exec for instance) bool MCStringCreateUnicodeStringFromData(MCDataRef p_data, bool p_is_external_rep, MCStringRef& r_string) { __MCAssertIsData(p_data); MCAssert(!p_is_external_rep); if (MCDataIsEmpty(p_data)) { r_string = MCValueRetain(kMCEmptyString); return true; } bool t_success; t_success = true; __MCString *self; self = nil; if (t_success) t_success = __MCValueCreate(kMCValueTypeCodeString, self); uint32_t t_byte_count; t_byte_count = MCDataGetLength(p_data); const byte_t* t_bytes = MCDataGetBytePtr(p_data); if (t_success) t_success = MCMemoryNewArray(t_byte_count + 1, self -> chars); if (t_success) { uindex_t i; for(i = 0; i < t_byte_count; i++) self -> chars[i] = t_bytes[i]; } if (t_success) { self -> flags |= kMCStringFlagIsNotNative; self -> char_count = t_byte_count; r_string = self; } else { if (self != nil) MCMemoryDeleteArray(self -> chars); MCMemoryDelete(self); } return t_success; } //////////////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF bool MCStringFormatV(MCStringRef& r_string, const char *p_format, va_list p_args) { MCAssert(nil != p_format); MCStringRef t_buffer; if (!MCStringCreateMutable(0, t_buffer)) return false; bool t_success; t_success = true; const char *t_format_ptr; t_format_ptr = p_format; while (t_success && *t_format_ptr != '\0') { const char *t_format_start_ptr, *t_format_end_ptr; t_format_start_ptr = t_format_end_ptr = t_format_ptr; bool t_has_range; t_has_range = false; // We need to track integer, floating-point and 'long double' argument // counts separately. The last group needs to be tracked individually as // it is passed in different registers on x86_64 to other floating-point // values. // // The integer types are themselves broken down by type, to ensure we // always use the correct type in the call to va_arg. We don't need to // track anything smaller than an int as the rules for '...' params say // that parameters are promoted to int/double. size_t t_int_arg_count = 0; size_t t_long_arg_count = 0; size_t t_llong_arg_count = 0; size_t t_ptr_arg_count = 0; size_t t_float_arg_count = 0; size_t t_ldouble_arg_count = 0; // Whether we are dealing with an MCValueRef or not bool t_is_valueref = false; // Loop until we encounter something that can't be handled by the C // library (i.e a ValueRef formatting command) or reach the end of the // formatting string. while (!t_is_valueref && *t_format_ptr != '\0') { // Is this a formatting command? if (*t_format_ptr != '%') { // Not a formatting command; continue t_format_ptr++; t_format_end_ptr = t_format_ptr; } else { // Move past the '%' and begin processing the formatting command t_format_ptr++; // Skip any flag characters at the beginning of the format while (*t_format_ptr == '-' || *t_format_ptr == '+' || *t_format_ptr == ' ' || *t_format_ptr == '#' || *t_format_ptr == '0') { t_format_ptr++; } // Skip any width specifier that is present bool t_indirect_width = false; if (*t_format_ptr == '*') { // Width will be specified via an argument of type int t_int_arg_count++; t_format_ptr++; t_indirect_width = true; } else { // Skip any digits specifying the width while (isdigit(*t_format_ptr)) t_format_ptr++; } // Skip any precision specifier that is present bool t_indirect_precision = false; if (*t_format_ptr == '.') { t_format_ptr++; if (*t_format_ptr == '*') { // Precision will be specified via an argument of type int t_int_arg_count++; t_format_ptr++; t_indirect_precision = true; } else { // Skip any digits specifying the precision while (isdigit(*t_format_ptr)) t_format_ptr++; } } // Process any size specifiers that are present size_t* t_type_count = NULL; switch (*t_format_ptr) { case 'h': // Char or short, depending on the number of 'h' chars t_format_ptr++; if (*t_format_ptr == 'h') t_format_ptr++; t_type_count = &t_int_arg_count; break; case 'l': // Long or long-long, depending on the number of 'l' chars t_format_ptr++; if (*t_format_ptr == 'l') { // Type is long-long t_format_ptr++; t_type_count = &t_llong_arg_count; } else { // Type is long t_type_count = &t_long_arg_count; } break; case 'j': // intmax_t or uintmax_t - these are aliases for long // long on all of our suppoerted platforms. t_format_ptr++; t_type_count = &t_llong_arg_count; break; case 'z': case 't': // size_t/ptrdiff_t - what's the underlying type? t_format_ptr++; if (sizeof(size_t) == sizeof(long long)) t_type_count = &t_llong_arg_count; else if (sizeof(size_t) == sizeof(long)) t_type_count = &t_long_arg_count; else /* if (sizeof(size_t) == sizeof(int)) */ t_type_count = &t_int_arg_count; break; case 'L': // Long double t_format_ptr++; t_type_count = &t_ldouble_arg_count; break; #ifdef _WIN32 case 'I': // MCVC-specific integer size specifiers if (strncmp(t_format_ptr, "I64", 3)) { t_type_count = &t_llong_arg_count; t_format_ptr += 3; } else if (strncmp(t_format_ptr, "I32", 3)) { t_type_count = &t_long_arg_count; t_format_ptr += 3; } else { t_type_count = &t_int_arg_count; t_format_ptr += 1; } #endif default: // Not a recognised size specifier. We'll resolve the // type_count pointer depending on the formatting type. t_type_count = NULL; break; } // Process the formatting specifier switch (*t_format_ptr) { case 'd': case 'i': case 'u': case 'o': case 'x': case 'X': case 'c': // Integer type - default to int if not specified if (t_type_count == NULL) t_type_count = &t_int_arg_count; break; case 'f': case 'F': case 'e': case 'E': case 'g': case 'G': case 'a': case 'A': // Floating-point type - default to double if not specified if (t_type_count == NULL) t_type_count = &t_float_arg_count; break; case 's': case 'p': // Pointer types. The size is ignored. t_type_count = &t_ptr_arg_count; break; case '@': // MCValueRef formatting. The parameter is a pointer // but we're not going to skip it - we'll actually // consume it directly, so don't increase the arg // count. t_type_count = NULL; t_is_valueref = true; // Same applies to any indirect width or precision if (t_indirect_width) { t_int_arg_count--; t_has_range = true; } if (t_indirect_precision) t_int_arg_count--; break; case '%': // A literal '%' character t_type_count = NULL; break; // We explicitly don't support 'n' as a format case 'n': default: // Oh dear. Something we didn't recognise! MCAssert(false); t_success = false; break; } // Skip the formatting specifier t_format_ptr++; // If this wasn't a valueref format, we can pass this formatting // command to the platforms string formatter. if (!t_is_valueref) t_format_end_ptr = t_format_ptr; // Update the count if (t_success && t_type_count != NULL) (*t_type_count)++; } } if (t_format_start_ptr != t_format_end_ptr) { char *t_format; size_t t_format_size; t_format_size = t_format_end_ptr - t_format_start_ptr; /* UNCHECKED */ t_format = (char *)malloc(t_format_size + 1); if (t_format == nil) t_success = false; char_t *t_string; uindex_t t_size; t_string = nil; if (t_success) { memcpy(t_format, t_format_start_ptr, t_format_size); t_format[t_format_size] = '\0'; #if defined(_WINDOWS) || defined(_WINDOWS_SERVER) t_success = MCNativeCharsFormatV(t_string, t_size, t_format, p_args); #else va_list t_args; va_copy(t_args, p_args); t_success = MCNativeCharsFormatV(t_string, t_size, t_format, t_args); va_end(t_args); #endif } if (t_success) t_success = MCStringAppendNativeChars(t_buffer, t_string, t_size); if (t_success) { // We now need to skip the arguments in the var args list that // were consumed by the native formatting function. while (t_int_arg_count > 0) { // Consume an integer argument (void)va_arg(p_args, int); t_int_arg_count--; } while (t_long_arg_count > 0) { // Consume a long integer argument (void)va_arg(p_args, long); t_long_arg_count--; } while (t_llong_arg_count > 0) { // Consume a long-long integer argument (void)va_arg(p_args, long long); t_llong_arg_count--; } while (t_ptr_arg_count > 0) { // Consume a pointer argument (void)va_arg(p_args, void*); t_ptr_arg_count--; } while (t_float_arg_count > 0) { // Consume a floating-point argument (void)va_arg(p_args, double); t_float_arg_count--; } while (t_ldouble_arg_count > 0) { // Consome a long-double argument (void)va_arg(p_args, long double); t_ldouble_arg_count--; } } MCMemoryDeallocate(t_string); free(t_format); } if (t_success && t_is_valueref) { const MCRange *t_range; if (t_has_range) t_range = va_arg(p_args, const MCRange *); else t_range = nil; MCValueRef t_value; t_value = va_arg(p_args, MCValueRef); MCAutoStringRef t_string; if (t_value == nullptr) t_string = MCSTR("(null)"); else if (MCValueGetTypeCode(t_value) == kMCValueTypeCodeString) t_string = (MCStringRef)t_value; else /* UNCHECKED */ MCValueCopyDescription (t_value, &t_string); if (t_range == nil) t_success = MCStringAppend(t_buffer, *t_string); else t_success = MCStringAppendSubstring(t_buffer, *t_string, *t_range); } // Process another chunk of the formatting string t_is_valueref = false; } if (t_success) t_success = MCStringCopyAndRelease(t_buffer, r_string); if (!t_success) MCValueRelease (t_buffer); return t_success; } MC_DLLEXPORT_DEF bool MCStringFormat(MCStringRef& r_string, const char *p_format, ...) { bool t_success; va_list t_args; va_start(t_args, p_format); t_success = MCStringFormatV(r_string, p_format, t_args); va_end(t_args); return t_success; } //////////////////////////////////////////////////////////////////////////////// static bool __MCStringCloneBuffer(MCStringRef self, unichar_t*& chars, uindex_t& char_count) { MCAssert(!__MCStringIsIndirect(self)); if (MCMemoryNewArray(self -> char_count + 1, chars)) { MCStrCharsMapFromUnicode(self -> chars, self -> char_count, chars, char_count); return true; } return false; } static bool __MCStringCloneNativeBuffer(MCStringRef self, char_t*& chars, uindex_t& char_count) { MCAssert(!__MCStringIsIndirect(self)); if (MCMemoryNewArray(self -> char_count + 1, chars)) { MCMemoryCopy(chars, self -> native_chars, self -> char_count); char_count = self -> char_count; return true; } return false; } MC_DLLEXPORT_DEF bool MCStringCopy(MCStringRef self, MCStringRef& r_new_string) { __MCAssertIsString(self); // If the string is immutable we can just bump the reference count. if (!MCStringIsMutable(self)) { r_new_string = self; MCValueRetain(self); return true; } // If it is mutable and indirect then retain the referenced string if (__MCStringIsIndirect(self)) { r_new_string = MCValueRetain(self -> string); return true; } // Otherwise make the mutable direct string immutable, // assign the new string as that, and make self indirect // referencing it. return __MCStringCopyMutable(self, r_new_string); } MC_DLLEXPORT_DEF bool MCStringCopyAndRelease(MCStringRef self, MCStringRef& r_new_string) { __MCAssertIsString(self); // If the string is immutable we just pass it through (as we are releasing the string). if (!MCStringIsMutable(self)) { r_new_string = self; return true; } // If the string is indirect then retain its reference, and release if (__MCStringIsIndirect(self)) { r_new_string = MCValueRetain(self -> string); MCValueRelease(self); return true; } // If the reference count is one, then shrink the buffer and mark as immutable. if (self -> references == 1) { __MCStringMakeImmutable(self); self -> flags &= ~kMCStringFlagIsMutable; self -> capacity = 0; r_new_string = self; return true; } // Otherwise, make a new indirect string if (!__MCStringMakeIndirect(self)) return false; // Reduce reference count self -> references -= 1; // And return a copy of the string r_new_string = MCValueRetain(self -> string); return true; } MC_DLLEXPORT_DEF bool MCStringMutableCopy(MCStringRef self, MCStringRef& r_new_string) { __MCAssertIsString(self); // If self is immutable, then the new mutable string will be indirect // referencing it. if (!MCStringIsMutable(self)) return __MCStringCreateIndirect(self, r_new_string); // If the string is already indirect, we just create a new reference to its string if (__MCStringIsIndirect(self)) return __MCStringCreateIndirect(self -> string, r_new_string); // If the string is mutable, we make it indirect and share // the indirect copy. if (!__MCStringMakeIndirect(self)) return false; return __MCStringCreateIndirect(self -> string, r_new_string); } MC_DLLEXPORT_DEF bool MCStringMutableCopyAndRelease(MCStringRef self, MCStringRef& r_new_string) { __MCAssertIsString(self); if (self -> references == 1) { if (!MCStringIsMutable(self)) { self -> flags |= kMCStringFlagIsMutable; //self -> capacity = self -> char_count; } r_new_string = self; return true; } if (!MCStringMutableCopy(self, r_new_string)) return false; self -> references -= 1; return true; } //////////////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF bool MCStringCopySubstring(MCStringRef self, MCRange p_range, MCStringRef& r_substring) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; // Avoid copying in case the substring is actually the whole string if (p_range . offset == 0 && self -> char_count < p_range . length) return MCStringCopy(self, r_substring); __MCStringClampRange(self, p_range); if (__MCStringIsNative(self)) return MCStringCreateWithNativeChars(self -> native_chars + p_range . offset, p_range . length, r_substring); return MCStringCreateWithChars(self -> chars + p_range . offset, p_range . length, r_substring); } MC_DLLEXPORT_DEF bool MCStringCopySubstringAndRelease(MCStringRef self, MCRange p_range, MCStringRef& r_substring) { if (MCStringCopySubstring(self, p_range, r_substring)) { MCValueRelease(self); return true; } return false; } MC_DLLEXPORT_DEF bool MCStringMutableCopySubstring(MCStringRef self, MCRange p_range, MCStringRef& r_new_string) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; __MCStringClampRange(self, p_range); // Simply create a mutable string with enough initial capacity and then copy // in the other strings chars. // Notice the slight amount of anal-retentiveness here in the use of a // temporary - any 'r_' (out) parameter should never be updated until the end // of the method and then only if the method is succeeding. MCStringRef t_new_string; if (__MCStringIsNative(self)) { if (!MCStringCreateMutable(p_range . length + 1, t_new_string)) return false; // Now copy across the chars (note we set the implicit NUL too, just to be // on the safe-side!). MCMemoryCopy(t_new_string -> native_chars, self -> native_chars + p_range . offset, p_range . length); t_new_string -> native_chars[p_range . length] = '\0'; } else { if (!MCStringCreateMutableUnicode(p_range . length + 1, t_new_string)) return false; MCMemoryCopy(t_new_string -> chars, self -> chars + p_range . offset, p_range . length * sizeof(strchar_t)); t_new_string -> chars[p_range . length] = '\0'; } t_new_string -> char_count = p_range . length; // Return the new string and success. r_new_string = t_new_string; return true; } MC_DLLEXPORT_DEF bool MCStringMutableCopySubstringAndRelease(MCStringRef self, MCRange p_range, MCStringRef& r_new_string) { if (MCStringMutableCopySubstring(self, p_range, r_new_string)) { MCValueRelease(self); return true; } return false; } //////////////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF bool MCStringCopyReversed(MCStringRef self, MCStringRef& r_new_string) { __MCAssertIsString(self); if (MCStringGetLength(self) < 2) return MCStringCopy(self, r_new_string); /* Make a deep copy of the input string. */ /* TODO[2017-03-29] This could be optimised by _not_ copying the * actual string buffer contents during the deep copy, but * reversing directly from the input string's internal buffer to * the result string's buffer. */ MCAutoStringRef t_result; if (!MCStringMutableCopy(self, &t_result)) return false; if (__MCStringIsIndirect(*t_result)) if (!__MCStringResolveIndirect(*t_result)) return false; if (__MCStringIsNative(*t_result)) { /* Native strings have one char_t per grapheme, so they can be * reversed in-place. */ MCInplaceReverse(t_result->native_chars, t_result->char_count); } else if (__MCStringIsTrivial(*t_result)) { /* Trivial strings have one unichar_t per grapheme, so they * can be reversed in-place. */ MCInplaceReverse(t_result->chars, t_result->char_count); } else { /* These strings have some potentially-unbounded number of * unichar_t codeunits items per grapheme. In this case, we * reverse by iterating over the contents of the original * string, copying the graphemes into the new string. */ MCStringRef t_original = self; if (__MCStringIsIndirect(t_original)) { t_original = t_original->string; } /* Start of the next grapheme to copy, in the input string */ uindex_t t_from = 0; uindex_t t_length = t_original->char_count; while (t_from < t_length) { /* Find the end of the current grapheme */ uindex_t t_grapheme_end = MCStringGraphemeBreakIteratorAdvance(t_original, t_from); if (t_grapheme_end == kMCLocaleBreakIteratorDone) t_grapheme_end = t_length; MCAssert(t_grapheme_end chars + t_length - t_grapheme_end, t_original->chars + t_from, (t_grapheme_end - t_from) * sizeof(*t_result->chars)); t_from = t_grapheme_end; } } r_new_string = t_result.Take(); return true; } //////////////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF bool MCStringIsMutable(const MCStringRef self) { __MCAssertIsString(self); return (self -> flags & kMCStringFlagIsMutable) != 0; } //////////////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF uindex_t MCStringGetLength(MCStringRef self) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; return __MCStringGetLength(self); } MC_DLLEXPORT_DEF const unichar_t *MCStringGetCharPtr(MCStringRef self) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return nil; if (!__MCStringUnnativize(self)) { return nil; } return self -> chars; } MC_DLLEXPORT_DEF const char_t *MCStringGetNativeCharPtr(MCStringRef self) { __MCAssertIsString(self); if (MCStringIsNative(self)) { // AL-2014-07-25: [[ Bug 12672 ]] Ensure possibly indirect string is resolved before returning char ptr if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return nil; return self -> native_chars; } return nil; } MC_DLLEXPORT_DEF const char_t *MCStringGetNativeCharPtrAndLength(MCStringRef self, uindex_t& r_char_count) { __MCAssertIsString(self); if (__MCStringNativize(self, r_char_count)) { return self->native_chars; } else { return nil; } } MC_DLLEXPORT_DEF unichar_t MCStringGetCharAtIndex(MCStringRef self, uindex_t p_index) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; /* Allow trailing null character */ MCAssert(p_index native_chars[p_index]); return self -> chars[p_index]; } MC_DLLEXPORT_DEF char_t MCStringGetNativeCharAtIndex(MCStringRef self, uindex_t p_index) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; /* Allow trailing null character */ MCAssert(p_index native_chars[p_index]; char_t t_native_char; if (MCUnicodeCharMapToNative(self -> chars[p_index], t_native_char)) return t_native_char; return '?'; } MC_DLLEXPORT_DEF codepoint_t MCStringGetCodepointAtIndex(MCStringRef self, uindex_t p_index) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; /* Allow trailing null character */ MCAssert(p_index native_chars[p_index]; return MCUnicodeCharMapFromNative(native_char); } // If the codeunit at the given index is a leading surrogate, and the next // one is a trailing surrogate then build the pair into a codepoint. if (MCUnicodeCodepointIsLeadingSurrogate(self->chars[p_index]) && MCUnicodeCodepointIsTrailingSurrogate(self->chars[p_index+1])) { return MCUnicodeSurrogatesToCodepoint(self->chars[p_index], self->chars[p_index+1]); } // The codeunit at the given index is not part of a (valid) surrogate pair. return self->chars[p_index]; } MC_DLLEXPORT_DEF uindex_t MCStringGetChars(MCStringRef self, MCRange p_range, unichar_t *p_chars) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; uindex_t t_count; t_count = 0; for(uindex_t i = p_range . offset; i < p_range . offset + p_range . length; i++) { if (i >= self -> char_count) break; if (__MCStringIsNative(self)) p_chars[i - p_range . offset] = MCUnicodeCharMapFromNative(self -> native_chars[i]); else p_chars[i - p_range . offset] = MCStrCharMapToUnicode(self -> chars[i]); t_count += 1; } return t_count; } MC_DLLEXPORT_DEF uindex_t MCStringGetNativeChars(MCStringRef self, MCRange p_range, char_t *p_chars) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; uindex_t t_count; t_count = 0; for(uindex_t i = p_range . offset; i < p_range . offset + p_range . length; i++) { if (i >= self -> char_count) break; if (__MCStringIsNative(self)) p_chars[i - p_range . offset] = self -> native_chars[i]; else p_chars[i - p_range . offset] = MCStrCharMapToNative(self -> chars[i]); t_count += 1; } return t_count; } MC_DLLEXPORT_DEF void MCStringNativize(MCStringRef self) { __MCAssertIsString(self); /* UNCHECKED */ __MCStringNativize(self); } static bool __MCStringConvertCopy(MCStringRef p_string, bool p_to_unicode, MCStringRef& r_copy) { __MCAssertIsString(p_string); if (MCStringIsNative(p_string) ^ p_to_unicode) return MCStringCopy(p_string, r_copy); MCAutoStringRef t_string; if (!MCStringMutableCopy(p_string, &t_string)) return false; if (p_to_unicode) { if (!__MCStringUnnativize(*t_string)) return false; } else { if (!__MCStringNativize(*t_string)) return false; } __MCStringMakeImmutable(*t_string); (*t_string) -> flags &= ~kMCStringFlagIsMutable; return MCStringCopy(*t_string, r_copy); } MC_DLLEXPORT_DEF bool MCStringNativeCopy(MCStringRef p_string, MCStringRef& r_copy) { return __MCStringConvertCopy(p_string, false, r_copy); } MC_DLLEXPORT_DEF bool MCStringUnicodeCopy(MCStringRef p_string, MCStringRef& r_copy) { return __MCStringConvertCopy(p_string, true, r_copy); } MC_DLLEXPORT_DEF bool MCStringIsNative(MCStringRef self) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; return __MCStringIsNative(self); } MC_DLLEXPORT_DEF bool MCStringCantBeEqualToNative(MCStringRef self, MCStringOptions p_options) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; return __MCStringCantBeEqualToNative(self, p_options); } MC_DLLEXPORT_DEF bool MCStringCanBeNative(MCStringRef self) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; return __MCStringCanBeNative(self); } // AL-2015-02-06: [[ Bug 14504 ]] Ensure 'basic' flag is checked against the direct string. MC_DLLEXPORT_DEF bool MCStringIsBasic(MCStringRef self) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; return __MCStringIsBasic(self); } // AL-2015-02-06: [[ Bug 14504 ]] Ensure 'trivial' flag is checked against the direct string. MC_DLLEXPORT_DEF bool MCStringIsTrivial(MCStringRef self) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; return __MCStringIsTrivial(self); } MC_DLLEXPORT_DEF bool MCStringMapCodepointIndices(MCStringRef self, MCRange p_in_range, MCRange &r_out_range) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; MCAssert(self != nil); // Shortcut for strings containing only BMP characters if (__MCStringIsBasic(self)) { __MCStringClampRange(self, p_in_range); r_out_range = p_in_range; return true; } uindex_t char_count = __MCStringGetLength(self); // Scan through the string, counting the number of codepoints uindex_t t_cp_counter = 0; uindex_t t_codeunit_pos = 0; MCRange t_units = MCRangeMake(0, 0); // If we are scanning the whole string, the end comes when all the codeunits have been processed // Otherwise, we have an amount of codepoints to read, not codeunits while (t_codeunit_pos < p_in_range . offset + p_in_range . length) { // Is this a single code unit or a valid surrogate pair? uindex_t t_length; if (MCStringIsValidSurrogatePair(self, t_codeunit_pos)) t_length = 2; else t_length = 1; // Update the appropriate field of the output if (t_codeunit_pos < p_in_range.offset) t_units.offset += t_length; else if (t_cp_counter < p_in_range.offset + p_in_range.length) t_units.length += t_length; // Make sure we haven't exceeded the length of the string if (t_units.offset > char_count) { t_units = MCRangeMake(char_count, 0); break; } if ((t_units.offset + t_units.length) > char_count) { t_units.length = char_count - t_units.offset; break; } ++t_cp_counter; t_codeunit_pos += t_length; } // All done r_out_range = t_units; return true; } MC_DLLEXPORT_DEF bool MCStringUnmapCodepointIndices(MCStringRef self, MCRange p_in_range, MCRange &r_out_range) { __MCAssertIsString(self); // AL-2015-02-06: [[ Bug 14504 ]] Use direct string for checks here, as the flags are not set on the indirect string. if (__MCStringIsIndirect(self)) self = self -> string; // Shortcut for strings containing only BMP characters if (__MCStringCanBeNative(self) || (__MCStringIsBasic(self))) { __MCStringClampRange(self, p_in_range); r_out_range = p_in_range; return true; } uindex_t char_count = __MCStringGetLength(self); // Check that the input indices are valid if (p_in_range.offset + p_in_range.length > char_count) return false; // Scan through the string, counting the number of code points uindex_t t_counter = 0; MCRange t_codepoints = MCRangeMake(0, 0); while (t_counter < p_in_range.offset + p_in_range.length) { // Is this a single code unit or a valid surrogate pair? uindex_t t_length; if (MCStringIsValidSurrogatePair(self, t_counter)) t_length = 2; else t_length = 1; // Increment the counters if (t_counter < p_in_range.offset) t_codepoints.offset++; else t_codepoints.length++; t_counter += t_length; } // All done r_out_range = t_codepoints; return true; } bool MCStringMapIndices(MCStringRef self, MCBreakIteratorType p_type, MCLocaleRef p_locale, MCRange p_in_range, MCRange &r_out_range) { __MCAssertIsString(self); __MCAssertIsLocale(p_locale); if (__MCStringIsIndirect(self)) self = self -> string; // Create the appropriate break iterator MCAutoCustomPointer t_iter; if (!MCLocaleBreakIteratorCreate(p_locale, p_type, &t_iter)) return false; // Set the iterator's text if (!MCLocaleBreakIteratorSetText(*t_iter, self)) { return false; } // Advance to the beginning of the specified range uindex_t t_start; t_start = MCLocaleBreakIteratorNext(*t_iter, p_in_range.offset); if (t_start == kMCLocaleBreakIteratorDone) { r_out_range = MCRangeMake(MCStringGetLength(self), 0); return true; } // Advance to the end of the specified range uindex_t t_end; t_end = MCLocaleBreakIteratorNext(*t_iter, p_in_range.length); if (t_end == kMCLocaleBreakIteratorDone) t_end = MCStringGetLength(self); MCRange t_units; t_units = MCRangeMakeMinMax(t_start, t_end); // All done r_out_range = t_units; return true; } MC_DLLEXPORT_DEF bool MCStringMapGraphemeIndices(MCStringRef self, MCRange p_grapheme_range, MCRange &r_cu_range) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; // Quick-n-dirty workaround if (__MCStringIsNative(self) || __MCStringIsTrivial(self)) { __MCStringClampRange(self, p_grapheme_range); r_cu_range = p_grapheme_range; return true; } // Find the beginning of the range uindex_t t_start; t_start = 0; while (p_grapheme_range . offset-- && t_start != kMCLocaleBreakIteratorDone) t_start = MCStringGraphemeBreakIteratorAdvance(self, t_start); // If there weren't enough graphemes, return 'empty' range if (t_start == kMCLocaleBreakIteratorDone) { r_cu_range = MCRangeMake(MCStringGetLength(self), 0); return true; } // Advance to the end of the specified range uindex_t t_end; t_end = t_start; while (p_grapheme_range . length-- && t_end != kMCLocaleBreakIteratorDone) t_end = MCStringGraphemeBreakIteratorAdvance(self, t_end); // If there weren't enough graphemesnst, return 'empty' range if (t_end == kMCLocaleBreakIteratorDone) t_end = MCStringGetLength(self); r_cu_range = MCRangeMakeMinMax(t_start, t_end); return true; } MC_DLLEXPORT_DEF bool MCStringCodepointIsWordPart(codepoint_t p_codepoint) { return MCUnicodeIsAlphabetic(p_codepoint) || MCUnicodeIsDigit(p_codepoint); } MC_DLLEXPORT_DEF bool MCStringMapTrueWordIndices(MCStringRef self, MCLocaleRef p_locale, MCRange p_in_range, MCRange &r_out_range) { __MCAssertIsString(self); __MCAssertIsLocale(p_locale); // Create the appropriate break iterator MCBreakIteratorRef t_iter; if (!MCLocaleBreakIteratorCreate(p_locale, kMCBreakIteratorTypeWord, t_iter)) return false; // Set the iterator's text if (!MCLocaleBreakIteratorSetText(t_iter, self)) { MCLocaleBreakIteratorRelease(t_iter); return false; } p_in_range . offset++; MCRange t_word_range = MCRangeMake(0, 0); bool t_found; // Advance to the beginning of the specified range while (p_in_range . offset-- && (t_found = MCLocaleWordBreakIteratorAdvance(self, t_iter, t_word_range))) ; if (!t_found) { r_out_range = MCRangeMake(MCStringGetLength(self), 0); MCLocaleBreakIteratorRelease (t_iter); return true; } // Advance to the end of the current word uindex_t t_start = t_word_range . offset; p_in_range . length--; // While more words are requested, find the end of the next word. while (p_in_range . length-- && MCLocaleWordBreakIteratorAdvance(self, t_iter, t_word_range)) ; MCRange t_units; t_units = MCRangeMakeMinMax(t_start, t_word_range . offset + t_word_range . length); // All done MCLocaleBreakIteratorRelease(t_iter); r_out_range = t_units; return true; } MC_DLLEXPORT_DEF bool MCStringMapSentenceIndices(MCStringRef self, MCLocaleRef p_locale, MCRange p_in_range, MCRange &r_out_range) { return MCStringMapIndices(self, kMCBreakIteratorTypeSentence, p_locale, p_in_range, r_out_range); } bool MCStringUnmapIndices(MCStringRef self, MCBreakIteratorType p_type, MCLocaleRef p_locale, MCRange p_in_range, MCRange &r_out_range) { __MCAssertIsString(self); __MCAssertIsLocale(p_locale); // Check that the input range is valid if (p_in_range.offset + p_in_range.length > MCStringGetLength(self)) return false; // Create a break iterator of the appropriate type MCAutoCustomPointer t_iter; if (!MCLocaleBreakIteratorCreate(p_locale, p_type, &t_iter)) return false; // Set the iterator's text if (!MCLocaleBreakIteratorSetText(*t_iter, self)) { return false; } // Count how many of the given unit it takes to reach or exceed the offset uindex_t t_start, t_offset; t_start = 0; t_offset = 0; while (t_offset < p_in_range.offset) { if (MCLocaleBreakIteratorIsBoundary(*t_iter, t_offset++)) t_start++; if (t_offset >= MCStringGetLength(self)) { r_out_range = MCRangeMake(t_offset, 0); return true; } } // Count how many more it takes to accomodate all the code units uindex_t t_end; t_end = 0; while (t_offset < p_in_range.offset + p_in_range.length) { if (MCLocaleBreakIteratorIsBoundary(*t_iter, t_offset++)) t_end++; if (t_offset >= MCStringGetLength(self)) break; } MCRange t_units; t_units = MCRangeMake(t_start, t_end); // All done r_out_range = t_units; return true; } static bool __MCStringFetchCodepointBefore(MCStringRef self, uindex_t& x_index, codepoint_t& r_cp) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; if (x_index > self -> char_count) return false; if (x_index == 0) return false; if (__MCStringIsBasic(self)) { r_cp = self -> chars[--x_index]; return true; } if (x_index > 1 && MCStringIsValidSurrogatePair(self, x_index - 2)) { r_cp = MCStringSurrogatesToCodepoint(self -> chars[x_index - 2], self -> chars[x_index - 1]); x_index -= 2; return true; } r_cp = self -> chars[--x_index]; return true; } static bool __MCStringFetchCodepointAfter(MCStringRef self, uindex_t& x_index, codepoint_t& r_cp) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; if (x_index >= self -> char_count) return false; if (__MCStringIsBasic(self)) { r_cp = self -> chars[x_index++]; return true; } if (MCStringIsValidSurrogatePair(self, x_index)) { r_cp = MCStringSurrogatesToCodepoint(self -> chars[x_index], self -> chars[x_index + 1]); x_index += 2; return true; } r_cp = self -> chars[x_index++]; return true; } uindex_t MCStringGraphemeBreakIteratorAdvance(MCStringRef self, uindex_t p_from) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsTrivial(self)) { if (p_from + 1 < self -> char_count) return p_from + 1; else return kMCLocaleBreakIteratorDone; } codepoint_t t_left_codepoint, t_right_codepoint; uindex_t t_next; t_next = p_from; uindex_t t_dummy; for (;;) { // Resolve the next two codepoints if (!__MCStringFetchCodepointAfter(self, t_next, t_left_codepoint)) return kMCLocaleBreakIteratorDone; t_dummy = t_next; if (!__MCStringFetchCodepointAfter(self, t_dummy, t_right_codepoint)) return kMCLocaleBreakIteratorDone; if (MCUnicodeIsGraphemeClusterBoundary(t_left_codepoint, t_right_codepoint)) break; } if (t_next >= self -> char_count) return kMCLocaleBreakIteratorDone; // This was a grapheme boundary, so t_next is the offset of the next // grapheme return t_next; } uindex_t MCStringGraphemeBreakIteratorRetreat(MCStringRef self, uindex_t p_from) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsTrivial(self)) { if (p_from > 0) return p_from - 1; else return kMCLocaleBreakIteratorDone; } codepoint_t t_left_codepoint, t_right_codepoint; uindex_t t_previous; t_previous = p_from; uindex_t t_dummy; for (;;) { // Resolve the next two codepoints if (!__MCStringFetchCodepointBefore(self, t_previous, t_right_codepoint)) return kMCLocaleBreakIteratorDone; t_dummy = t_previous; if (!__MCStringFetchCodepointBefore(self, t_dummy, t_left_codepoint)) return kMCLocaleBreakIteratorDone; if (MCUnicodeIsGraphemeClusterBoundary(t_left_codepoint, t_right_codepoint)) break; } if (t_previous == 0) return kMCLocaleBreakIteratorDone; // This was a grapheme boundary, so t_next is the offset of the next // grapheme return t_previous; } uindex_t __MCStringCountGraphemesInRange(MCStringRef self, MCRange p_cu_range) { MCAssert(!__MCStringIsIndirect(self)); uindex_t t_grapheme_count; t_grapheme_count = 0; uindex_t t_cu_offset; t_cu_offset = p_cu_range . offset; while (t_cu_offset < p_cu_range . offset + p_cu_range . length) { t_cu_offset = MCStringGraphemeBreakIteratorAdvance(self, t_cu_offset); t_grapheme_count++; if (t_cu_offset == kMCLocaleBreakIteratorDone) break; } return t_grapheme_count; } MC_DLLEXPORT_DEF bool MCStringUnmapGraphemeIndices(MCStringRef self, MCRange p_cu_range, MCRange &r_char_range) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; __MCStringClampRange(self, p_cu_range); if (__MCStringCanBeNative(self) || __MCStringIsTrivial(self)) { r_char_range = p_cu_range; return true; } MCRange t_char_range; // First determine the grapheme offset by counting t_char_range . offset = __MCStringCountGraphemesInRange(self, MCRangeMake(0, p_cu_range . offset)); t_char_range . length = __MCStringCountGraphemesInRange(self, p_cu_range); r_char_range = t_char_range; return true; } MC_DLLEXPORT_DEF bool MCStringUnmapTrueWordIndices(MCStringRef self, MCLocaleRef p_locale, MCRange p_in_range, MCRange &r_out_range) { __MCAssertIsString(self); __MCAssertIsLocale(p_locale); if (__MCStringIsIndirect(self)) self = self->string; // Check that the input range is valid if (p_in_range.offset + p_in_range.length > __MCStringGetLength(self)) return false; // Create a break iterator of the appropriate type MCBreakIteratorRef t_iter; if (!MCLocaleBreakIteratorCreate(p_locale, kMCBreakIteratorTypeWord, t_iter)) return false; // Set the iterator's text if (!MCLocaleBreakIteratorSetText(t_iter, self)) { MCLocaleBreakIteratorRelease(t_iter); return false; } // Count how many words it takes to reach or exceed the offset uindex_t t_start, t_left_break, t_right_break; t_start = 0; t_right_break = 0; t_left_break = 0; while (t_right_break < p_in_range.offset) { t_right_break++; if (MCLocaleBreakIteratorIsBoundary(t_iter, t_right_break)) { // if the intervening chars contain a letter or number then it was a valid 'word' while (t_left_break < t_right_break) { codepoint_t t_cp = MCStringGetCodepointAtIndex(self, t_left_break); if (MCStringCodepointIsWordPart(t_cp)) break; t_left_break++; if (t_cp > UNICHAR_MAX) t_left_break++; } if (t_left_break < t_right_break) t_start++; t_left_break = t_right_break; } if (t_right_break >= __MCStringGetLength(self)) { r_out_range = MCRangeMake(t_right_break, 0); MCLocaleBreakIteratorRelease (t_iter); return true; } } // Count how many more it takes to accomodate all the code units uindex_t t_end; t_end = 0; while (t_right_break < p_in_range.offset + p_in_range.length) { t_right_break++; if (MCLocaleBreakIteratorIsBoundary(t_iter, t_right_break)) { // if the intervening chars contain a letter or number then it was a valid 'word' while (t_left_break < t_right_break) { // NB: GetCodepointAtIndex takes a code-unit index codepoint_t t_cp = MCStringGetCodepointAtIndex(self, t_left_break); if (MCStringCodepointIsWordPart(t_cp)) break; // If the char is in the SMP, it is two codeunits long. t_left_break += MCUnicodeCodepointGetCodeunitLength(t_cp); } if (t_left_break < t_right_break) t_end++; t_left_break = t_right_break; } if (t_right_break >= __MCStringGetLength(self)) break; } MCRange t_units; t_units = MCRangeMake(t_start, t_end); // All done MCLocaleBreakIteratorRelease(t_iter); r_out_range = t_units; return true; } MC_DLLEXPORT_DEF bool MCStringUnmapSentenceIndices(MCStringRef self, MCLocaleRef p_locale, MCRange p_in_range, MCRange &r_out_range) { return MCStringUnmapIndices(self, kMCBreakIteratorTypeSentence, p_locale, p_in_range, r_out_range); } extern MCLocaleRef kMCLocaleBasic; MC_DLLEXPORT_DEF bool MCStringMapIndices(MCStringRef self, MCCharChunkType p_type, MCRange p_char_range, MCRange &r_cu_range) { __MCAssertIsString(self); MCChunkType t_simple_chunk_type; t_simple_chunk_type = MCChunkTypeSimplify(self, MCChunkTypeFromCharChunkType(p_type)); switch (t_simple_chunk_type) { case kMCChunkTypeCodeunit: r_cu_range = p_char_range; return true; case kMCChunkTypeCodepoint: return MCStringMapCodepointIndices(self, p_char_range, r_cu_range); case kMCChunkTypeCharacter: return MCStringMapGraphemeIndices(self, p_char_range, r_cu_range); default: MCUnreachableReturn(false); } } MC_DLLEXPORT_DEF bool MCStringUnmapIndices(MCStringRef self, MCCharChunkType p_type, MCRange p_cu_range, MCRange &r_char_range) { __MCAssertIsString(self); MCChunkType t_simple_chunk_type; t_simple_chunk_type = MCChunkTypeSimplify(self, MCChunkTypeFromCharChunkType(p_type)); switch (t_simple_chunk_type) { case kMCChunkTypeCodeunit: r_char_range = p_cu_range; return true; case kMCChunkTypeCodepoint: return MCStringUnmapCodepointIndices(self, p_cu_range, r_char_range); case kMCChunkTypeCharacter: return MCStringUnmapGraphemeIndices(self, p_cu_range, r_char_range); default: MCUnreachableReturn(false); } } //////////////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF bool MCStringConvertToBytes(MCStringRef self, MCStringEncoding p_encoding, bool p_is_external_rep, byte_t*& r_bytes, uindex_t& r_byte_count) { __MCAssertIsString(self); MCAssert(!p_is_external_rep); switch(p_encoding) { // [[ Bug 12204 ]] textEncode ASCII support is actually native case kMCStringEncodingASCII: return MCStringConvertToAscii(self, (char_t*&)r_bytes, r_byte_count); case kMCStringEncodingNative: return MCStringConvertToNative(self, (char_t*&)r_bytes, r_byte_count); case kMCStringEncodingUTF16: { uindex_t t_char_count; if (MCStringConvertToUnicode(self, (unichar_t*&)r_bytes, t_char_count)) { r_byte_count = t_char_count * sizeof(unichar_t); return true; } return false; } // AL-2014-31-03: [[ Bug 12067 ]] Implement conversion to big endian bytes. case kMCStringEncodingUTF16LE: case kMCStringEncodingUTF16BE: { uindex_t t_char_count; unichar_t * t_bytes; if (!MCStringConvertToUnicode(self, t_bytes, t_char_count)) return false; if ((p_encoding == kMCStringEncodingUTF16BE && kMCByteOrderHost != kMCByteOrderBigEndian) || (p_encoding == kMCStringEncodingUTF16LE && kMCByteOrderHost != kMCByteOrderLittleEndian)) { for (uindex_t i = 0; i < t_char_count; ++i) { t_bytes[i] = MCSwapInt(t_bytes[i]); } } r_bytes = reinterpret_cast(t_bytes); r_byte_count = t_char_count * sizeof(*t_bytes); return true; } case kMCStringEncodingUTF8: return MCStringConvertToUTF8(self, (char*&)r_bytes, r_byte_count); case kMCStringEncodingUTF32: { uindex_t t_char_count; uint32_t *t_codepoints; if (MCStringConvertToUTF32(self, t_codepoints, t_char_count)) { r_bytes = (byte_t*) t_codepoints; r_byte_count = t_char_count * sizeof(uint32_t); return true; } } break; case kMCStringEncodingUTF32BE: case kMCStringEncodingUTF32LE: { uindex_t t_char_count; uint32_t* t_codepoints; if (MCStringConvertToUTF32(self, t_codepoints, t_char_count)) { for (uinteger_t i = 0 ; i < t_char_count ; ++i) { if (p_encoding == kMCStringEncodingUTF32BE) t_codepoints[i] = MCSwapInt32HostToBig(t_codepoints[i]); else t_codepoints[i] = MCSwapInt32HostToLittle(t_codepoints[i]); } r_bytes = (byte_t*)t_codepoints; r_byte_count = t_char_count * sizeof(uint32_t); return true; } } break; #if !defined(__ISO_8859_1__) case kMCStringEncodingISO8859_1: break; #endif #ifndef __WINDOWS__ case kMCStringEncodingWindows1252: break; #endif #if !defined(__MAC__) && !defined(__IOS__) case kMCStringEncodingMacRoman: break; #endif } return false; } MC_DLLEXPORT_DEF bool MCStringConvertToAscii(MCStringRef self, char_t *&r_chars, uindex_t& r_char_count) { __MCAssertIsString(self); // Get the native chars, but excludes any char belonging to the extended part of the ASCII - char_t *t_chars; uindex_t t_char_count = MCStringGetLength(self); if (!MCMemoryNewArray(t_char_count + 1, t_chars)) return false; t_char_count = MCStringGetNativeChars(self, MCRangeMake(0, t_char_count), t_chars); for (uindex_t i = 0; i < t_char_count; ++i) { if (t_chars[i] > 127) t_chars[i] = '?'; } r_chars = t_chars; r_char_count = t_char_count; return true; } MC_DLLEXPORT_DEF bool MCStringConvertToUnicode(MCStringRef self, unichar_t*& r_chars, uindex_t& r_char_count) { __MCAssertIsString(self); // Allocate an array of chars one bigger than needed. As the allocated array // is filled with zeros, this will naturally NUL terminate the string. unichar_t *t_chars; if (!MCMemoryNewArray(MCStringGetLength(self) + 1, t_chars)) return false; r_char_count = MCStringGetChars(self, MCRangeMake(0, MCStringGetLength(self)), t_chars); r_chars = t_chars; return true; } MC_DLLEXPORT_DEF bool MCStringNormalizeAndConvertToNative(MCStringRef string, char_t*& r_chars, uindex_t& r_char_count) { __MCAssertIsString(string); MCAutoStringRef t_normalized; if (!MCStringNormalizedCopyNFC(string, &t_normalized)) return false; return MCStringConvertToNative(*t_normalized, r_chars, r_char_count); } MC_DLLEXPORT_DEF bool MCStringConvertToNative(MCStringRef self, char_t*& r_chars, uindex_t& r_char_count) { __MCAssertIsString(self); // Allocate an array of chars one byte bigger than needed. As the allocated array // is filled with zeros, this will naturally NUL terminate the string. char_t *t_chars; if (!MCMemoryNewArray(MCStringGetLength(self) + 1, t_chars)) return false; r_char_count = MCStringGetNativeChars(self, MCRangeMake(0, MCStringGetLength(self)), t_chars); r_chars = t_chars; return true; } MC_DLLEXPORT_DEF bool MCStringNormalizeAndConvertToCString(MCStringRef string, char*& r_cstring) { __MCAssertIsString(string); MCAutoStringRef t_normalized; if (!MCStringNormalizedCopyNFC(string, &t_normalized)) return false; return MCStringConvertToCString(*t_normalized, r_cstring); } MC_DLLEXPORT_DEF bool MCStringConvertToCString(MCStringRef p_string, char*& r_cstring) { __MCAssertIsString(p_string); uindex_t t_length; t_length = MCStringGetLength(p_string); if (!MCMemoryNewArray(t_length + 1, r_cstring)) return false; MCStringGetNativeChars(p_string, MCRangeMake(0, t_length), (char_t*)r_cstring); r_cstring[t_length] = '\0'; return true; } MC_DLLEXPORT_DEF bool MCStringConvertToWString(MCStringRef p_string, unichar_t*& r_wstring) { __MCAssertIsString(p_string); uindex_t t_length; t_length = MCStringGetLength(p_string); if (!MCMemoryNewArray(t_length + 1, r_wstring)) return false; MCStringGetChars(p_string, MCRangeMake(0, t_length), r_wstring); r_wstring[t_length] = '\0'; return true; } MC_DLLEXPORT_DEF bool MCStringConvertToUTF8String(MCStringRef p_string, char*& r_utf8string) { __MCAssertIsString(p_string); uindex_t length_is_ignored; return MCStringConvertToUTF8(p_string, r_utf8string, length_is_ignored); } MC_DLLEXPORT_DEF bool MCStringConvertToUTF8(MCStringRef p_string, char*& r_utf8string, uindex_t& r_utf8_chars) { __MCAssertIsString(p_string); // Allocate an array of chars one byte bigger than needed. As the allocated array // is filled with zeros, this will naturally NUL terminate the string. uindex_t t_length; uindex_t t_byte_count; unichar_t* t_unichars; t_length = MCStringGetLength(p_string); if (!MCMemoryNewArray(t_length + 1, t_unichars)) return false; uindex_t t_char_count = MCStringGetChars(p_string, MCRangeMake(0, t_length), t_unichars); t_byte_count = MCUnicodeCharsMapToUTF8(t_unichars, t_char_count, nil, 0); if (!MCMemoryNewArray(t_byte_count + 1, r_utf8string)) { MCMemoryDeleteArray (t_unichars); return false; } MCUnicodeCharsMapToUTF8(t_unichars, t_char_count, (byte_t*)r_utf8string, t_byte_count); r_utf8_chars = t_byte_count; // Delete temporary unichar_t array MCMemoryDeleteArray(t_unichars); return true; } MC_DLLEXPORT_DEF bool MCStringConvertToUTF32(MCStringRef self, uint32_t *&r_codepoints, uinteger_t &r_char_count) { __MCAssertIsString(self); if (MCStringIsNative(self)) { // Shortcut for native string - no surrogate pair checking uindex_t t_char_count; const char_t* t_chars; uint32_t *t_codepoints; t_chars = MCStringGetNativeCharPtrAndLength(self, t_char_count); // SN-2015-07-03: [[ Bug 15571 ]] Allocate the right size if (!MCMemoryAllocate((t_char_count + 1) * sizeof(uint32_t), t_codepoints)) return false; for (uindex_t i = 0 ; i < t_char_count; ++i) t_codepoints[i] = (uint32_t)t_chars[i]; r_codepoints = t_codepoints; r_char_count = t_char_count; return true; } else { uindex_t t_char_count; uindex_t t_codepoint_count; const unichar_t *t_unichars; MCAutoArray t_codepoints; bool t_invalid_char; t_unichars = MCStringGetCharPtr(self); t_char_count = MCStringGetLength(self); t_codepoint_count = 0; t_invalid_char = false; if (!t_codepoints . New(t_char_count + 1)) return false; // Loop up to the penultimate char, to avoid the checking of the index each // time a trail surrogate must be found - a pair may be broken if imported for instance uindex_t i = 0; for (; i < t_char_count - 1 && !t_invalid_char; ++i) { if (t_unichars[i] > 0xD7FF && t_unichars[i] < 0xDC00) { // Surrogate lead found if (t_unichars[i+1] > 0xDBFF && t_unichars[i+1] < 0xE000) { // SN-2015-07-03: [[ Bug 15571 ]] Codepoint and UTF-16 index will not // remain the same if any surrogate pair appears. // Surogate trail found: valid surrogate pair t_codepoints[t_codepoint_count] = MCUnicodeSurrogatesToCodepoint(t_unichars[i], t_unichars[i+1]); ++i; } else t_invalid_char = true; } else { // SN-2015-07-03: [[ Bug 15571 ]] Codepoint and UTF-16 index will not // remain the same if any surrogate pair appears. t_codepoints[t_codepoint_count] = (uint32_t)t_unichars[i]; } ++t_codepoint_count; } if (t_invalid_char) return false; // Add the last codeunit if (i < t_char_count) { // SN-2015-07-03: [[ Bug 15571 ]] Codepoint and UTF-16 index will not // remain the same if any surrogate pair appears. t_codepoints[t_codepoint_count] = (uint32_t)t_unichars[i]; ++t_codepoint_count; } t_codepoints . Shrink(t_codepoint_count + 1); t_codepoints . Take(r_codepoints, r_char_count); // SN-2015-07-03: [[ Bug 15571 ]] The NULL codepoint is not amongst the // char count. r_char_count = t_codepoint_count; return true; } return false; } #ifdef __WINDOWS__ MC_DLLEXPORT_DEF bool MCStringConvertToBSTR(MCStringRef p_string, BSTR& r_bstr) { uindex_t t_length; unichar_t* t_chars; t_length = MCStringGetLength(p_string); if (!MCMemoryNewArray(t_length + 1, t_chars)) return false; MCStringGetChars(p_string, MCRangeMake(0, t_length), t_chars); r_bstr = SysAllocString((OLECHAR*)t_chars); MCMemoryDeleteArray(t_chars); if (r_bstr == nil) return false; return true; } #endif //////////////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF hash_t MCStringHash(MCStringRef self, MCStringOptions p_options) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsNative(self)) return __MCNativeOp_Hash(self -> native_chars, self -> char_count, p_options); return MCUnicodeHash(self -> chars, self -> char_count, (MCUnicodeCompareOption)p_options); } MC_DLLEXPORT_DEF bool MCStringIsEqualTo(MCStringRef self, MCStringRef p_other, MCStringOptions p_options) { __MCAssertIsString(self); __MCAssertIsString(p_other); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsIndirect(p_other)) p_other = p_other -> string; if (self == p_other) return true; if (__MCStringIsEmpty(self) != __MCStringIsEmpty(p_other)) return false; bool self_native, other_native; self_native = __MCStringIsNative(self); other_native = __MCStringIsNative(p_other); if ((self_native && __MCStringCantBeEqualToNative(p_other, p_options)) || (other_native && __MCStringCantBeEqualToNative(self, p_options))) return false; if (self_native && other_native) { return __MCNativeOp_IsEqualTo(self -> native_chars, self -> char_count, p_other -> native_chars, p_other -> char_count, p_options); } return MCUnicodeCompare(self -> chars, self -> char_count, self_native, p_other -> chars, p_other -> char_count, other_native, (MCUnicodeCompareOption)p_options) == 0; } MC_DLLEXPORT_DEF bool MCStringIsEmpty(MCStringRef string) { return string == nil || MCStringGetLength(string) == 0; } template static inline bool __MCStringIsInteger(const T *p_chars, uindex_t p_length) { uindex_t i = 0; if (p_chars[i] == '-') i++; if (i == p_length) return false; if (p_chars[i] == '0') { return i + 1 == p_length; } for(; i < p_length; i++) { if (!isdigit(p_chars[i])) { return false; } } return true; } MC_DLLEXPORT_DEF bool MCStringIsInteger(MCStringRef self) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsNative(self)) return __MCStringIsInteger(self->native_chars, self->char_count); return __MCStringIsInteger(self->chars, self->char_count); } MC_DLLEXPORT_DEF bool MCStringSubstringIsEqualTo(MCStringRef self, MCRange p_sub, MCStringRef p_other, MCStringOptions p_options) { __MCAssertIsString(self); __MCAssertIsString(p_other); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsIndirect(p_other)) p_other = p_other -> string; __MCStringClampRange(self, p_sub); bool self_native = __MCStringIsNative(self); if (self_native) { if (__MCStringIsNative(p_other)) return __MCNativeOp_IsEqualTo(self -> native_chars + p_sub . offset, p_sub . length, p_other -> native_chars, p_other -> char_count, p_options); if (__MCStringCantBeEqualToNative(p_other, p_options)) return false; } const void *self_chars; if (self_native) self_chars = self -> native_chars + p_sub . offset; else self_chars = self -> chars + p_sub . offset; return MCUnicodeCompare(self_chars, p_sub . length, self_native, p_other -> chars, p_other -> char_count, __MCStringIsNative(p_other), (MCUnicodeCompareOption)p_options) == 0; } MC_DLLEXPORT_DEF bool MCStringSubstringIsEqualToSubstring(MCStringRef self, MCRange p_sub, MCStringRef p_other, MCRange p_other_sub, MCStringOptions p_options) { __MCAssertIsString(self); __MCAssertIsString(p_other); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsIndirect(p_other)) p_other = p_other -> string; __MCStringClampRange(self, p_sub); __MCStringClampRange(p_other, p_other_sub); bool self_native = __MCStringIsNative(self); bool other_native = __MCStringIsNative(p_other); if (self_native && other_native) { return __MCNativeOp_IsEqualTo(self -> native_chars + p_sub . offset, p_sub . length, p_other -> native_chars + p_other_sub . offset, p_other_sub . length, p_options); } const void *self_chars, *other_chars; if (self_native) self_chars = self -> native_chars + p_sub . offset; else self_chars = self -> chars + p_sub . offset; if (other_native) other_chars = p_other-> native_chars + p_other_sub . offset; else other_chars = p_other -> chars + p_other_sub . offset; return MCUnicodeCompare(self_chars, p_sub . length, self_native, other_chars, p_other_sub . length, other_native, (MCUnicodeCompareOption)p_options) == 0; } MC_DLLEXPORT_DEF bool MCStringIsEqualToNativeChars(MCStringRef self, const char_t *p_chars, uindex_t p_char_count, MCStringOptions p_options) { return MCStringSubstringIsEqualToNativeChars(self, MCRangeMake(0, UINDEX_MAX), p_chars, p_char_count, p_options); } MC_DLLEXPORT_DEF bool MCStringSubstringIsEqualToNativeChars(MCStringRef self, MCRange p_range, const char_t *p_chars, uindex_t p_char_count, MCStringOptions p_options) { __MCAssertIsString(self); MCAssert(nil != p_chars); if (MCStringIsNative(self)) { if (__MCStringIsIndirect(self)) self = self -> string; __MCStringClampRange(self, p_range); return __MCNativeOp_IsEqualTo(self -> native_chars + p_range . offset, p_range . length, p_chars, p_char_count, p_options); } if (MCStringCantBeEqualToNative(self, p_options)) return false; MCAutoStringRef t_string; MCStringCreateWithNativeChars(p_chars, p_char_count, &t_string); return MCStringSubstringIsEqualTo(self, p_range, *t_string, p_options); } MC_DLLEXPORT_DEF compare_t MCStringCompareTo(MCStringRef self, MCStringRef p_other, MCStringOptions p_options) { __MCAssertIsString(self); __MCAssertIsString(p_other); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsIndirect(p_other)) p_other = p_other -> string; if (__MCStringIsNative(self) && __MCStringIsNative(p_other)) { return __MCNativeOp_CompareTo(self -> native_chars, self -> char_count, p_other -> native_chars, p_other -> char_count, p_options); } return MCUnicodeCompare(self -> chars, self -> char_count, __MCStringIsNative(self), p_other -> chars, p_other -> char_count, __MCStringIsNative(p_other), (MCUnicodeCompareOption)p_options); } MC_DLLEXPORT_DEF bool MCStringBeginsWith(MCStringRef self, MCStringRef p_prefix, MCStringOptions p_options, uindex_t *r_self_match_length) { __MCAssertIsString(self); __MCAssertIsString(p_prefix); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsIndirect(p_prefix)) p_prefix = p_prefix -> string; if (__MCStringIsNative(self)) { if (__MCStringIsNative(p_prefix)) { if (!__MCNativeOp_BeginsWith(self -> native_chars, self -> char_count, p_prefix -> native_chars, p_prefix -> char_count, p_options)) return false; if (r_self_match_length != nil) *r_self_match_length = p_prefix -> char_count; return true; } if (__MCStringCantBeEqualToNative(p_prefix, p_options)) return false; } return MCUnicodeBeginsWith(self -> chars, self -> char_count, __MCStringIsNative(self), p_prefix -> chars, p_prefix -> char_count, __MCStringIsNative(p_prefix), (MCUnicodeCompareOption)p_options, r_self_match_length); } MC_DLLEXPORT_DEF bool MCStringSharedPrefix(MCStringRef self, MCRange p_range, MCStringRef p_prefix, MCStringOptions p_options, uindex_t& r_self_match_length) { __MCAssertIsString(self); __MCAssertIsString(p_prefix); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsIndirect(p_prefix)) p_prefix = p_prefix -> string; __MCStringClampRange(self, p_range); bool self_native = __MCStringIsNative(self); if (self_native) { if (__MCStringIsNative(p_prefix)) { r_self_match_length = __MCNativeOp_SharedPrefix(self -> native_chars + p_range . offset, p_range . length, p_prefix -> native_chars, p_prefix -> char_count, p_options); return r_self_match_length == p_prefix -> char_count; } if (__MCStringCantBeEqualToNative(p_prefix, p_options)) return false; } uindex_t t_prefix_share; const void *self_chars; if (self_native) self_chars = self -> native_chars + p_range . offset; else self_chars = self -> chars + p_range . offset; MCUnicodeSharedPrefix(self_chars, p_range . length, self_native, p_prefix -> chars, p_prefix -> char_count, MCStringIsNative(p_prefix), (MCUnicodeCompareOption)p_options, r_self_match_length, t_prefix_share); return t_prefix_share == __MCStringGetLength(p_prefix); } MC_DLLEXPORT_DEF bool MCStringBeginsWithCString(MCStringRef self, const char_t *p_prefix_cstring, MCStringOptions p_options) { __MCAssertIsString(self); MCAssert(nil != p_prefix_cstring); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsNative(self)) { return __MCNativeOp_BeginsWith(self -> native_chars, self -> char_count, p_prefix_cstring, strlen((const char *)p_prefix_cstring), p_options); } MCAutoStringRef t_string; MCStringCreateWithNativeChars(p_prefix_cstring, strlen((const char *)p_prefix_cstring), &t_string); return MCStringBeginsWith(self, *t_string, p_options); } MC_DLLEXPORT_DEF bool MCStringEndsWith(MCStringRef self, MCStringRef p_suffix, MCStringOptions p_options, uindex_t *r_self_match_length) { __MCAssertIsString(self); __MCAssertIsString(p_suffix); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsIndirect(p_suffix)) p_suffix = p_suffix -> string; if (__MCStringIsNative(self)) { if (__MCStringIsNative(p_suffix)) { if (!__MCNativeOp_EndsWith(self -> native_chars, self -> char_count, p_suffix -> native_chars, p_suffix -> char_count, p_options)) return false; if (r_self_match_length != nil) *r_self_match_length = p_suffix -> char_count; return true; } if (__MCStringCantBeEqualToNative(p_suffix, p_options)) return false; } return MCUnicodeEndsWith(self -> chars, self -> char_count, __MCStringIsNative(self), p_suffix -> chars, p_suffix -> char_count, __MCStringIsNative(p_suffix), (MCUnicodeCompareOption)p_options, r_self_match_length); } MC_DLLEXPORT_DEF bool MCStringSharedSuffix(MCStringRef self, MCRange p_range, MCStringRef p_suffix, MCStringOptions p_options, uindex_t& r_self_match_length) { __MCAssertIsString(self); __MCAssertIsString(p_suffix); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsIndirect(p_suffix)) p_suffix = p_suffix -> string; __MCStringClampRange(self, p_range); bool self_native = __MCStringIsNative(self); if (self_native) { if (__MCStringIsNative(p_suffix)) { r_self_match_length = __MCNativeOp_SharedSuffix(self -> native_chars + p_range . offset, p_range . length, p_suffix -> native_chars, p_suffix -> char_count, p_options); return r_self_match_length == p_suffix -> char_count; } if (__MCStringCantBeEqualToNative(p_suffix, p_options)) return false; } uindex_t t_suffix_share; const void *self_chars; if (self_native) self_chars = self -> native_chars + p_range . offset; else self_chars = self -> chars + p_range . offset; MCUnicodeSharedSuffix(self_chars, p_range . length, self_native, p_suffix -> chars, p_suffix -> char_count, __MCStringIsNative(p_suffix), (MCUnicodeCompareOption)p_options, r_self_match_length, t_suffix_share); return t_suffix_share == MCStringGetLength(p_suffix); } MC_DLLEXPORT_DEF bool MCStringEndsWithCString(MCStringRef self, const char_t *p_suffix_cstring, MCStringOptions p_options) { __MCAssertIsString(self); MCAssert(nil != p_suffix_cstring); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsNative(self)) { return __MCNativeOp_EndsWith(self -> native_chars, self -> char_count, p_suffix_cstring, strlen((const char *)p_suffix_cstring), p_options); } MCAutoStringRef t_string; MCStringCreateWithNativeChars(p_suffix_cstring, strlen((const char *)p_suffix_cstring), &t_string); return MCStringEndsWith(self, *t_string, p_options); } MC_DLLEXPORT_DEF bool MCStringContains(MCStringRef self, MCStringRef p_needle, MCStringOptions p_options) { __MCAssertIsString(self); __MCAssertIsString(p_needle); if (MCStringIsEmpty(p_needle)) return false; if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsIndirect(p_needle)) p_needle = p_needle -> string; if (__MCStringIsNative(self)) { if (__MCStringIsNative(p_needle)) { return __MCNativeOp_Contains(self -> native_chars, self -> char_count, p_needle -> native_chars, p_needle -> char_count, p_options); } if (__MCStringCantBeEqualToNative(p_needle, p_options)) return false; } return MCUnicodeContains(self -> chars, self -> char_count, __MCStringIsNative(self), p_needle -> chars, p_needle -> char_count, __MCStringIsNative(p_needle), (MCUnicodeCompareOption)p_options); } MC_DLLEXPORT_DEF bool MCStringSubstringContains(MCStringRef self, MCRange p_range, MCStringRef p_needle, MCStringOptions p_options) { __MCAssertIsString(self); __MCAssertIsString(p_needle); if (__MCStringIsIndirect(p_needle)) p_needle = p_needle -> string; // SN-2014-09-05: [[ Bug 13346 ]] Empty is *never* contained in a string. In the loop, a commong string of length 0 // will be found, which unfortunaly matches the length of the empty needle. if (__MCStringIsEmpty(p_needle)) return false; if (__MCStringIsIndirect(self)) self = self -> string; __MCStringClampRange(self, p_range); bool self_native = __MCStringIsNative(self); if (self_native) { if (__MCStringIsNative(p_needle)) { return __MCNativeOp_Contains(self -> native_chars + p_range . offset, p_range . length, p_needle -> native_chars, p_needle -> char_count, p_options); } if (__MCStringCantBeEqualToNative(p_needle, p_options)) return false; } const void *self_chars; if (self_native) self_chars = self -> native_chars + p_range . offset; else self_chars = self -> chars + p_range . offset; return MCUnicodeContains(self_chars, p_range . length, self_native, p_needle -> chars, p_needle -> char_count, __MCStringIsNative(p_needle), (MCUnicodeCompareOption)p_options); } //////////////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF bool MCStringFirstIndexOf(MCStringRef self, MCStringRef p_needle, uindex_t p_after, MCStringOptions p_options, uindex_t& r_offset) { return MCStringFirstIndexOfStringInRange(self, p_needle, MCRangeMake(p_after, UINDEX_MAX), p_options, r_offset); } MC_DLLEXPORT_DEF bool MCStringFirstIndexOfStringInRange(MCStringRef self, MCStringRef p_needle, MCRange p_range, MCStringOptions p_options, uindex_t& r_offset) { __MCAssertIsString(self); __MCAssertIsString(p_needle); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsIndirect(p_needle)) p_needle = p_needle -> string; __MCStringClampRange(self, p_range); bool self_native = __MCStringIsNative(self); if (self_native) { if (__MCStringIsNative(p_needle)) { size_t t_relative_offset; if (!__MCNativeOp_FirstIndexOf(self -> native_chars + p_range . offset, p_range . length, p_needle -> native_chars, p_needle -> char_count, p_options, t_relative_offset)) return false; r_offset = p_range . offset + t_relative_offset; return true; } if (__MCStringCantBeEqualToNative(p_needle, p_options)) return false; } bool t_result; const void *self_chars; if (self_native) self_chars = self -> native_chars + p_range . offset; else self_chars = self -> chars + p_range . offset; // AL-2014-09-05: [[ Bug 13352 ]] Crash due to not taking into account p_after by adjusting length of string. t_result = MCUnicodeFirstIndexOf(self_chars, p_range . length, self_native, p_needle -> chars, p_needle -> char_count, __MCStringIsNative(p_needle), (MCUnicodeCompareOption)p_options, r_offset); // Correct the output index if (t_result == true) r_offset += p_range . offset; return t_result; } MC_DLLEXPORT_DEF bool MCStringFirstIndexOfChar(MCStringRef self, codepoint_t p_needle, uindex_t p_after, MCStringOptions p_options, uindex_t& r_offset) { return MCStringFirstIndexOfCharInRange(self, p_needle, MCRangeMakeMinMax(p_after, self -> char_count), p_options, r_offset); } MC_DLLEXPORT_DEF bool MCStringFirstIndexOfCharInRange(MCStringRef self, codepoint_t p_needle, MCRange p_range, MCStringOptions p_options, uindex_t& r_offset) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; __MCStringClampRange(self, p_range); if (__MCStringIsNative(self)) { char_t t_mapped_needle; if (p_needle > UNICHAR_MAX || !MCUnicodeCharMapToNative(p_needle, t_mapped_needle)) return false; size_t t_relative_offset; if (!__MCNativeOp_FirstIndexOf(self -> native_chars + p_range . offset, p_range . length, &t_mapped_needle, 1, p_options, t_relative_offset)) return false; r_offset = p_range . offset + t_relative_offset; return true; } bool t_result; t_result = MCUnicodeFirstIndexOfChar(self -> chars + p_range . offset, p_range . length, p_needle, (MCUnicodeCompareOption)p_options, r_offset); // Correct the output index if (t_result == true) r_offset += p_range . offset; return t_result; } MC_DLLEXPORT_DEF bool MCStringLastIndexOf(MCStringRef self, MCStringRef p_needle, uindex_t p_before, MCStringOptions p_options, uindex_t& r_offset) { return MCStringLastIndexOfStringInRange(self, p_needle, MCRangeMake(0, p_before), p_options, r_offset); } MC_DLLEXPORT_DEF bool MCStringLastIndexOfStringInRange(MCStringRef self, MCStringRef p_needle, MCRange p_range, MCStringOptions p_options, uindex_t& r_offset) { __MCAssertIsString(self); __MCAssertIsString(p_needle); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsIndirect(p_needle)) p_needle = p_needle -> string; __MCStringClampRange(self, p_range); if (__MCStringIsNative(self)) { if (__MCStringIsNative(p_needle)) { size_t t_relative_offset; if (!__MCNativeOp_LastIndexOf(self -> native_chars + p_range . offset, p_range . length, p_needle -> native_chars, p_needle -> char_count, p_options, t_relative_offset)) return false; r_offset = p_range . offset + t_relative_offset; return true; } if (__MCStringCantBeEqualToNative(p_needle, p_options)) return false; } return MCUnicodeLastIndexOf(self -> chars + p_range . offset, p_range . length, __MCStringIsNative(self), p_needle -> chars, p_needle -> char_count, __MCStringIsNative(p_needle), (MCUnicodeCompareOption)p_options, r_offset); } MC_DLLEXPORT_DEF bool MCStringLastIndexOfChar(MCStringRef self, codepoint_t p_needle, uindex_t p_before, MCStringOptions p_options, uindex_t& r_offset) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; // Make sure the after index is in range. p_before = MCMin(p_before, self -> char_count); if (__MCStringIsNative(self)) { char_t t_mapped_needle; if (p_needle > UNICHAR_MAX || !MCUnicodeCharMapToNative((unichar_t)p_needle, t_mapped_needle)) return false; size_t t_relative_offset; if (!__MCNativeOp_LastIndexOf(self -> native_chars, p_before, &t_mapped_needle, 1, p_options, t_relative_offset)) return false; r_offset = (uindex_t)t_relative_offset; return true; } return MCUnicodeLastIndexOfChar(self -> chars, p_before, p_needle, (MCUnicodeCompareOption)p_options, r_offset); } static bool __MCStringFind(MCStringRef self, MCRange p_range, MCStringRef p_needle, MCStringOptions p_options, MCRange *r_result) { bool self_native = __MCStringIsNative(self); if (self_native) { if (__MCStringIsNative(p_needle)) { size_t t_relative_offset; if (!__MCNativeOp_FirstIndexOf(self -> native_chars + p_range . offset, p_range . length, p_needle -> native_chars, p_needle -> char_count, p_options, t_relative_offset)) return false; if (r_result != nil) { r_result -> offset = p_range . offset + t_relative_offset; r_result -> length = p_needle -> char_count; } return true; } if (MCStringCantBeEqualToNative(p_needle, p_options)) return false; } // Circumvent performance hit due to possibility of case / form sensitivity affecting delimiter search. // TODO: Implement properly, based on properties of the needle string. if (__MCStringGetLength(p_needle) == 0) { return false; } else if (__MCStringGetLength(p_needle) == 1) { codepoint_t t_codepoint = MCStringGetCodepointAtIndex(p_needle, 0); // if codepoint is among first 64 ASCII characters then do case and form sensitive comparison. if (t_codepoint < 0x41) p_options = kMCStringOptionCompareExact; } // Similar to contains, this searches for needle but only with range of self. // It also returns the the range in self that needle occupies (but only if // r_result is non-nil). MCRange t_range; if (!MCUnicodeFind(self->native_chars + (self_native ? p_range . offset : 2 * p_range . offset), p_range . length, __MCStringIsNative(self), p_needle -> chars, p_needle -> char_count, __MCStringIsNative(p_needle), (MCUnicodeCompareOption)p_options, t_range)) return false; if (r_result != nil) { // Correct the range t_range.offset += p_range.offset; *r_result = t_range; } return true; } MC_DLLEXPORT_DEF bool MCStringFind(MCStringRef self, MCRange p_range, MCStringRef p_needle, MCStringOptions p_options, MCRange *r_result) { __MCAssertIsString(self); __MCAssertIsString(p_needle); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsIndirect(p_needle)) p_needle = p_needle -> string; __MCStringClampRange(self, p_range); return __MCStringFind(self, p_range, p_needle, p_options, r_result); } static uindex_t MCStringCountStrChars(MCStringRef self, MCRange p_range, const void *p_needle_chars, uindex_t p_needle_char_count, bool p_needle_native, MCStringOptions p_options) { if (__MCStringIsIndirect(self)) self = self -> string; // Keep track of how many occurrences have been found. uindex_t t_count; t_count = 0; __MCStringClampRange(self, p_range); bool self_native = __MCStringIsNative(self); const void *self_chars; if (self_native) self_chars = self -> native_chars + p_range . offset; else self_chars = self -> chars + p_range . offset; // Loop through the char range checking for occurrences of needle. uindex_t t_offset; t_offset = p_range . offset; while(t_offset < p_range . offset + p_range . length) { // Compute the length of the shared prefix at the current offset. uindex_t t_prefix_length; uindex_t t_ignored; MCUnicodeSharedPrefix((const char *)self_chars + (self_native ? t_offset : (t_offset * 2)), p_range . offset + p_range . length - t_offset, self_native, p_needle_chars, p_needle_char_count, p_needle_native, (MCUnicodeCompareOption)p_options, t_ignored, t_prefix_length); // If we find a match, increase the count and move past it, otherwise // just bump. if (t_prefix_length == p_needle_char_count) { t_offset += t_prefix_length; t_count += 1; } else t_offset += 1; } // Return the number of occurrences. return t_count; } MC_DLLEXPORT_DEF uindex_t MCStringCount(MCStringRef self, MCRange p_range, MCStringRef p_needle, MCStringOptions p_options) { __MCAssertIsString(self); __MCAssertIsString(p_needle); if (__MCStringIsIndirect(p_needle)) p_needle = p_needle -> string; if (MCStringIsNative(self)) { if (__MCStringIsNative(p_needle)) return __MCNativeOp_Count(self -> native_chars + p_range . offset, p_range . length, p_needle -> native_chars, p_needle -> char_count, p_options, nil); if (__MCStringCantBeEqualToNative(p_needle, p_options)) return 0; } uindex_t t_count = MCStringCountStrChars(self, p_range, p_needle -> chars, p_needle -> char_count, __MCStringIsNative(p_needle), p_options); return t_count; } MC_DLLEXPORT_DEF uindex_t MCStringCountChar(MCStringRef self, MCRange p_range, codepoint_t p_needle, MCStringOptions p_options) { __MCAssertIsString(self); strchar_t t_native_needle; t_native_needle = (strchar_t)p_needle; if (MCStringIsNative(self)) { char_t t_mapped_needle; if (p_needle > UNICHAR_MAX || !MCUnicodeCharMapToNative((unichar_t)p_needle, t_mapped_needle)) return 0; return __MCNativeOp_Count(self -> native_chars + p_range . offset, p_range . length, &t_mapped_needle, 1, p_options, nil); } return MCStringCountStrChars(self, p_range, &t_native_needle, 1, false, p_options); } //////////////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF bool MCStringDivideAtChar(MCStringRef self, codepoint_t p_separator, MCStringOptions p_options, MCStringRef& r_head, MCStringRef& r_tail) { __MCAssertIsString(self); uindex_t t_offset; if (!MCStringFirstIndexOfChar(self, p_separator, 0, p_options, t_offset)) { if (!MCStringCopy(self, r_head)) return false; r_tail = MCValueRetain(kMCEmptyString); return true; } return MCStringDivideAtIndex(self, t_offset, r_head, r_tail); } MC_DLLEXPORT_DEF bool MCStringDivideAtIndex(MCStringRef self, uindex_t p_offset, MCStringRef& r_head, MCStringRef& r_tail) { __MCAssertIsString(self); MCStringRef t_head; if (!MCStringCopySubstring(self, MCRangeMake(0, p_offset), t_head)) return false; MCStringRef t_tail; if (!MCStringCopySubstring(self, MCRangeMakeMinMax(p_offset + 1, MCStringGetLength(self)), t_tail)) { MCValueRelease(t_head); return false; } r_head = t_head; r_tail = t_tail; return true; } //////////////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF bool MCStringBreakIntoChunks(MCStringRef self, codepoint_t p_separator, MCStringOptions p_options, MCRange*& r_ranges, uindex_t& r_range_count) { __MCAssertIsString(self); MCAssert(p_separator < 128); uindex_t t_length; t_length = MCStringGetLength(self); // Count the number of chunks, adjusting for an empty trailing chunk. uindex_t t_range_count; // SN-2014-11-13: [[ Bug 13993 ]] No delimiter found means 1 range, 1 delimiter means 2 ranges, etc. t_range_count = MCStringCountChar(self, MCRangeMake(0, MCStringGetLength(self)), p_separator, p_options) + 1; if (t_length > 0 && MCStringGetNativeCharAtIndex(self, t_length - 1) == p_separator) t_range_count -= 1; // Allocate the range array. MCRange *t_ranges; if (!MCMemoryNewArray(t_range_count, t_ranges)) return false; // Now compute the ranges. uindex_t t_prev_offset, t_offset, t_index; t_prev_offset = 0; t_offset = 0; t_index = 0; for(;;) { // SN-2014-11-13: [[ Bug 13993 ]] The offset might be after the last char, if the previous delimiter // was the last char of the string. We are done in that case. if (t_prev_offset == MCStringGetLength(self)) break; if (!MCStringFirstIndexOfChar(self, p_separator, t_prev_offset, p_options, t_offset)) { t_ranges[t_index] . offset = t_prev_offset; t_ranges[t_index] . length = t_length - t_prev_offset; break; } t_ranges[t_index] . offset = t_prev_offset; t_ranges[t_index] . length = t_offset - t_prev_offset; t_prev_offset = t_offset + 1; } r_ranges = t_ranges; r_range_count = t_range_count; return true; } //////////////////////////////////////////////////////////////////////////////// // Skip 'count' occurrences of 'needle' in 'range' of 'self' according to 'options'. // If 'needle' is not found, false is returned and r_last is untouched. static bool __MCStringSkip(MCStringRef self, MCRange p_range, MCStringRef p_needle, uindex_t p_count, MCStringOptions p_options, MCRange& r_last) { MCAssert(p_count > 0); // Optimize the case of both needle and self native. if (__MCStringIsNative(self) && __MCStringIsNative(p_needle)) { size_t t_last_offset; if (!__MCNativeOp_Skip(self -> native_chars + p_range . offset, p_range . length, p_needle -> native_chars, p_needle -> char_count, p_count, p_options, &t_last_offset)) return false; r_last . offset = p_range . offset + t_last_offset; r_last . length = p_needle -> char_count; return true; } uindex_t t_start, t_finish; t_start = p_range . offset; t_finish = p_range . offset + p_range . length; MCRange t_last; while(p_count > 0) { if (!__MCStringFind(self, MCRangeMakeMinMax(t_start, t_finish), p_needle, p_options, &t_last)) return false; // Decrement the number of needles to skip. p_count -= 1; // Move the considered range to start at the end of the previous // needle. t_start = t_last . offset + t_last . length; } r_last = t_last; return true; } // Count occurrences of 'needle' in 'range' of 'self' according to 'options'. // If no occurrences are found 0 is returned and r_last is untouched. static uindex_t __MCStringCount(MCStringRef self, MCRange p_range, MCStringRef p_needle, MCStringOptions p_options, MCRange *r_last) { // Optimize the case of both needle and self native. if (__MCStringIsNative(self) && __MCStringIsNative(p_needle)) { size_t t_last_offset; size_t t_count; t_count = __MCNativeOp_Count(self -> native_chars + p_range . offset, p_range . length, p_needle -> native_chars, p_needle -> char_count, p_options, &t_last_offset); if (t_count > 0 && r_last != nil) { r_last -> offset = p_range . offset + t_last_offset; r_last -> length = p_needle -> char_count; } return t_count; } uindex_t t_count; t_count = 0; uindex_t t_start, t_finish; t_start = p_range . offset; t_finish = p_range . offset + p_range . length; MCRange t_last; t_last = MCRangeMake(t_start, 0); while(__MCStringFind(self, MCRangeMakeMinMax(t_start, t_finish), p_needle, p_options, &t_last)) { // Increment the number of needles found. t_count += 1; // Move the considered range to start at the end of the previous // needle. t_start = t_last . offset + t_last . length; } if (t_count > 0 && r_last != nil) *r_last = t_last; return t_count; } //////////////////////////////////////////////////////////////////////////////// static bool __MCStringDelimitedOffset(MCStringRef self, MCRange p_range, MCStringRef p_needle, MCStringRef p_delimiter, uindex_t p_skip, MCStringOptions p_options, uindex_t& r_index, MCRange *r_found, MCRange *r_before, MCRange *r_after) { // Compute the absolute start and finish points of the considered range. // This is much easier to work with than offset / length. // We gradually move 'start' forward past delimiters (and finally the // found string) as the algorithm progresses. uindex_t t_start, t_finish; t_start = p_range . offset; t_finish = p_range . offset + p_range . length; // Skip 'skip' delimiters, if this is not possible then we are done. This // computes the initial 'previous delimiter'. MCRange t_prev_delimiter; t_prev_delimiter = MCRangeMake(t_start, 0); if (p_skip > 0 && !__MCStringSkip(self, p_range, p_delimiter, p_skip, p_options, t_prev_delimiter)) return false; // The initial number of delimiters is the number we skip. uindex_t t_delimiter_count; t_delimiter_count = p_skip; // Make sure we start from the end of the last delimiter skipped. t_start = t_prev_delimiter . offset + t_prev_delimiter . length; // Now search for 'needle' in the remaining range. If nothing is found, then // we are done. MCRange t_found_range; if (!__MCStringFind(self, MCRangeMakeMinMax(t_start, t_finish), p_needle, p_options, &t_found_range)) return false; // We must now search for delimiters in the substring between the end of the // previous delimiter and the start of the found range. t_delimiter_count += __MCStringCount(self, MCRangeMakeMinMax(t_start, t_found_range . offset), p_delimiter, p_options, &t_prev_delimiter); // Return the (absolute) number of delimiters encountered. r_index = t_delimiter_count; // Return the range of the found string (if required). if (r_found != nil) *r_found = t_found_range; // Return the range of the delimiter before the found string (if required). if (r_before != nil) *r_before = t_prev_delimiter; // Finally, if 'r_after' is required, we must find the first delimiter // after the found string. if (r_after != nil) { // Update start to be after the found range. t_start = t_found_range . offset + t_found_range . length; MCRange t_next_delimiter; if (!__MCStringFind(self, MCRangeMake(t_start, t_finish - t_start), p_delimiter, p_options, &t_next_delimiter)) t_next_delimiter = MCRangeMake(t_finish, 0); *r_after = t_next_delimiter; } return true; } MC_DLLEXPORT_DEF bool MCStringDelimitedOffset(MCStringRef self, MCRange p_range, MCStringRef p_needle, MCStringRef p_delimiter, uindex_t p_skip, MCStringOptions p_options, uindex_t& r_index, MCRange *r_found, MCRange *r_before, MCRange *r_after) { __MCAssertIsString(self); __MCAssertIsString(p_needle); __MCAssertIsString(p_delimiter); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsIndirect(p_needle)) p_needle = p_needle -> string; if (__MCStringIsIndirect(p_delimiter)) p_delimiter = p_delimiter -> string; __MCStringClampRange(self, p_range); if (__MCStringIsEmpty(p_needle)) return false; if (__MCStringIsNative(self) && __MCStringIsNative(p_needle) && __MCStringIsNative(p_delimiter) && __MCStringGetLength(p_delimiter) == 1) { size_t t_index, t_found, t_before, t_after; if (!__MCNativeOp_ForwardCharDelimitedOffset(self -> native_chars + p_range . offset, p_range . length, p_needle -> native_chars, p_needle -> char_count, p_delimiter -> native_chars[0], p_skip, p_options, t_index, r_found != nil ? &t_found : nil, r_before != nil ? &t_before : nil, r_after != nil ? &t_after : nil)) return false; r_index = t_index; if (r_found != nil) { r_found -> offset = p_range . offset + t_found; r_found -> length = p_needle -> char_count; } if (r_before != nil) { if (t_index > p_skip) { r_before -> offset = p_range . offset + t_before; r_before -> length = 1; } else { r_before -> offset = p_range . offset; r_before -> length = 0; } } if (r_after != nil) { if (t_after < p_range . length) { r_after -> offset = p_range . offset + t_after; r_after -> length = 1; } else { r_after -> offset = p_range . offset + p_range . length; r_after -> length = 0; } } return true; } return __MCStringDelimitedOffset(self, p_range, p_needle, p_delimiter, p_skip, p_options, r_index, r_found, r_before, r_after); } //////////////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF bool MCStringForwardDelimitedRegion(MCStringRef self, MCRange p_range, MCStringRef p_delimiter, MCRange p_region, MCStringOptions p_options, MCRange& r_range) { __MCAssertIsString(self); __MCAssertIsString(p_delimiter); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsIndirect(p_delimiter)) p_delimiter = p_delimiter -> string; __MCStringClampRange(self, p_range); uindex_t t_start, t_finish; t_start = p_range . offset; t_finish = p_range . offset + p_range . length; MCRange t_first_del; t_first_del = MCRangeMake(t_start, 0); if (p_region . offset > 0 && !__MCStringSkip(self, MCRangeMakeMinMax(t_start, t_finish), p_delimiter, p_region . offset, p_options, t_first_del)) { r_range = MCRangeMake(t_finish, 0); return true; } t_start = t_first_del . offset + t_first_del . length; if (p_region . length == 0) { r_range = MCRangeMakeMinMax(t_start, 0); return true; } MCRange t_last_del; if (!__MCStringSkip(self, MCRangeMakeMinMax(t_start, t_finish), p_delimiter, p_region . length, p_options, t_last_del)) { r_range = MCRangeMakeMinMax(t_start, t_finish); return true; } r_range = MCRangeMakeMinMax(t_start, t_last_del . offset); return true; } //////////////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF bool MCStringFold(MCStringRef self, MCStringOptions p_options) { MCAssert(MCStringIsMutable(self)); // If we are looking for exact comparison then folding has no effect. if (p_options == kMCStringOptionCompareExact || p_options == kMCStringOptionCompareNonliteral) return true; // Ensure the string is not indirect. if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; if (__MCStringIsNative(self)) { MCNativeCharsLowercase(self -> native_chars, self -> char_count); __MCStringChanged(self, true, true, true); return true; } // Case-fold the string unichar_t *t_folded; uindex_t t_folded_length; if (!MCUnicodeCaseFold(self -> chars, self -> char_count, t_folded, t_folded_length)) return false; // Update the string MCMemoryDeleteArray(self -> chars); self -> chars = t_folded; self -> char_count = t_folded_length; __MCStringChanged(self); // We always succeed (at the moment) return true; } MC_DLLEXPORT_DEF bool MCStringLowercase(MCStringRef self, MCLocaleRef p_locale) { MCAssert(MCStringIsMutable(self)); // Ensure the string is not indirect. if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; if (__MCStringIsNative(self)) { MCNativeCharsLowercase(self -> native_chars, self -> char_count); __MCStringChanged(self, true, true, true); return true; } // Case transformations can change string lengths unichar_t *t_lowered; uindex_t t_lowered_length; if (!MCUnicodeLowercase(p_locale, self -> chars, self -> char_count, t_lowered, t_lowered_length)) return false; MCMemoryDeleteArray(self -> chars); self -> chars = t_lowered; self -> char_count = t_lowered_length; __MCStringChanged(self); // We always succeed (at the moment) return true; } MC_DLLEXPORT_DEF bool MCStringUppercase(MCStringRef self, MCLocaleRef p_locale) { MCAssert(MCStringIsMutable(self)); // Ensure the string is not indirect. if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; if (__MCStringIsNative(self)) { MCNativeCharsUppercase(self -> native_chars, self -> char_count); __MCStringChanged(self, true, true, true); return true; } // Case transformations can change string lengths unichar_t *t_lowered; uindex_t t_lowered_length; if (!MCUnicodeUppercase(p_locale, self -> chars, self -> char_count, t_lowered, t_lowered_length)) return false; MCMemoryDeleteArray(self -> chars); self -> chars = t_lowered; self -> char_count = t_lowered_length; __MCStringChanged(self); // We always succeed (at the moment) return true; } //////////////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF bool MCStringAppend(MCStringRef self, MCStringRef p_suffix) { MCAssert(MCStringIsMutable(self)); if (__MCStringIsIndirect(p_suffix)) p_suffix = p_suffix -> string; // Only do the append now if self != suffix. if (self != p_suffix) { if (__MCStringIsNative(p_suffix)) return MCStringAppendNativeChars(self, p_suffix -> native_chars, p_suffix -> char_count); return MCStringAppendChars(self, p_suffix -> chars, p_suffix -> char_count); } // Otherwise copy and recurse. MCAutoStringRef t_suffix_copy; MCStringCopy(p_suffix, &t_suffix_copy); return MCStringAppend(self, *t_suffix_copy); } MC_DLLEXPORT_DEF bool MCStringAppendSubstring(MCStringRef self, MCStringRef p_suffix, MCRange p_range) { MCAssert(MCStringIsMutable(self)); if (__MCStringIsIndirect(p_suffix)) p_suffix = p_suffix -> string; // Only do the append now if self != suffix. if (self != p_suffix) { __MCStringClampRange(p_suffix, p_range); if (__MCStringIsNative(p_suffix)) return MCStringAppendNativeChars(self, p_suffix -> native_chars + p_range . offset, p_range . length); return MCStringAppendChars(self, p_suffix -> chars + p_range . offset, p_range . length); } // Otherwise copy substring and append. MCAutoStringRef t_suffix_substring; return MCStringCopySubstring(p_suffix, p_range, &t_suffix_substring) && MCStringAppend(self, *t_suffix_substring); } MC_DLLEXPORT_DEF bool MCStringAppendNativeChars(MCStringRef self, const char_t *p_chars, uindex_t p_char_count) { MCAssert(MCStringIsMutable(self)); // Ensure the string is not indirect. if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; // Ensure we have enough room in self - with the gap at the end. if (!__MCStringExpandAt(self, self -> char_count, p_char_count)) return false; if (__MCStringIsNative(self)) { MCMemoryCopy(self -> native_chars + self -> char_count - p_char_count, p_chars, p_char_count); self -> native_chars[self -> char_count] = '\0'; __MCStringChanged(self, true, true, true); return true; } // Now copy the chars across. for(uindex_t i = 0; i < p_char_count; i++) self -> chars[i + self -> char_count - p_char_count] = MCUnicodeCharMapFromNative(p_chars[i]); // Set the NULL self -> chars[self -> char_count] = '\0'; // Appending native chars cannot change the simple status __MCStringChanged(self); // We succeeded. return true; } MC_DLLEXPORT_DEF bool MCStringAppendChars(MCStringRef self, const unichar_t *p_chars, uindex_t p_char_count) { MCAssert(MCStringIsMutable(self)); // Ensure the string is not indirect. if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; // Ensure we have enough room in self - with the gap at the end. if (!__MCStringExpandAt(self, self -> char_count, p_char_count)) return false; // If we are native, attempt a native copy of the input chars. if (__MCStringIsNative(self)) { bool t_not_native; t_not_native = false; for(uindex_t i = 0; i < p_char_count; i++) if (!MCUnicodeCharMapToNative(p_chars[i], self -> native_chars[i + self -> char_count - p_char_count])) { t_not_native = true; break; } if (!t_not_native) { self -> native_chars[self -> char_count] = '\0'; __MCStringChanged(self, true, true, true); return true; } // At least one of the additional chars was not native. __MCStringShrinkAt(self, self -> char_count - p_char_count, p_char_count); if (!__MCStringUnnativize(self)) { return false; } return MCStringAppendChars(self, p_chars, p_char_count); } // Copy the chars across recomputing whether the string can be native at // the same time. bool t_can_be_native; t_can_be_native = __MCStringCopyChars(self -> chars + self -> char_count - p_char_count, p_chars, p_char_count, __MCStringCanBeNative(self)); // Set the NULL self -> chars[self -> char_count] = '\0'; __MCStringChanged(self, false, false, t_can_be_native); // We succeeded. return true; } MC_DLLEXPORT_DEF bool MCStringAppendNativeChar(MCStringRef self, char_t p_char) { return MCStringAppendNativeChars(self, &p_char, 1); } MC_DLLEXPORT_DEF bool MCStringAppendChar(MCStringRef self, unichar_t p_char) { return MCStringAppendChars(self, &p_char, 1); } MC_DLLEXPORT_DEF bool MCStringAppendCodepoint (MCStringRef self, codepoint_t p_codepoint) { uindex_t t_num_units; unichar_t t_units[2]; t_num_units = MCStringCodepointToSurrogates (p_codepoint, t_units); return MCStringAppendChars (self, t_units, t_num_units); } MC_DLLEXPORT_DEF bool MCStringPrepend(MCStringRef self, MCStringRef p_prefix) { MCAssert(MCStringIsMutable(self)); if (__MCStringIsIndirect(p_prefix)) p_prefix = p_prefix -> string; // Only do the prepend now if self != prefix. if (self != p_prefix) { if (__MCStringIsNative(p_prefix)) return MCStringPrependNativeChars(self, p_prefix -> native_chars, p_prefix -> char_count); return MCStringPrependChars(self, p_prefix -> chars, p_prefix -> char_count); } // Otherwise copy and recurse. MCAutoStringRef t_prefix_copy; MCStringCopy(p_prefix, &t_prefix_copy); return MCStringPrepend(self, *t_prefix_copy); } MC_DLLEXPORT_DEF bool MCStringPrependSubstring(MCStringRef self, MCStringRef p_prefix, MCRange p_range) { MCAssert(MCStringIsMutable(self)); if (__MCStringIsIndirect(p_prefix)) p_prefix = p_prefix -> string; // Only do the prepend now if self != prefix. if (self != p_prefix) { __MCStringClampRange(p_prefix, p_range); if (__MCStringIsNative(p_prefix)) return MCStringAppendNativeChars(self, p_prefix -> native_chars + p_range . offset, p_range . length); return MCStringAppendChars(self, p_prefix -> chars + p_range . offset, p_range . length); } // Otherwise copy substring and prepend. MCAutoStringRef t_prefix_substring; return MCStringCopySubstring(p_prefix, p_range, &t_prefix_substring) && MCStringPrepend(self, *t_prefix_substring); } MC_DLLEXPORT_DEF bool MCStringPrependNativeChars(MCStringRef self, const char_t *p_chars, uindex_t p_char_count) { MCAssert(MCStringIsMutable(self)); // Ensure the string is not indirect. if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; // Ensure we have enough room in self - with the gap at the beginning. if (!__MCStringExpandAt(self, 0, p_char_count)) return false; if (__MCStringIsNative(self)) { MCMemoryCopy(self -> native_chars, p_chars, p_char_count); __MCStringChanged(self, true, true, true); return true; } // Now copy the chars across. for(uindex_t i = 0; i < p_char_count; i++) self -> chars[i] = MCUnicodeCharMapFromNative(p_chars[i]); // Prepending native chars cannot change the simple status __MCStringChanged(self); // We succeeded. return true; } MC_DLLEXPORT_DEF bool MCStringPrependChars(MCStringRef self, const unichar_t *p_chars, uindex_t p_char_count) { MCAssert(MCStringIsMutable(self)); // Ensure the string is not indirect. if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; // Ensure we have enough room in self - with the gap at the end. if (!__MCStringExpandAt(self, 0, p_char_count)) return false; // If we are native, attempt a native copy of the input chars. if (__MCStringIsNative(self)) { bool t_not_native; t_not_native = false; for(uindex_t i = 0; i < p_char_count; i++) // SN-2014-05-20 [[ Bug 12344 ]] [[ Bug 12345 ]] // Prepending chars was appending them if (!MCUnicodeCharMapToNative(p_chars[i], self -> native_chars[i])) { t_not_native = true; break; } if (!t_not_native) { self -> native_chars[self -> char_count] = '\0'; __MCStringChanged(self, true, true, true); return true; } // At least one of the additional chars was not native. __MCStringShrinkAt(self, 0, p_char_count); if (!__MCStringUnnativize(self)) { return false; } return MCStringPrependChars(self, p_chars, p_char_count); } // Copy the chars across recomputing whether the string can be native at // the same time. bool t_can_be_native; t_can_be_native = __MCStringCopyChars(self -> chars, p_chars, p_char_count, __MCStringCanBeNative(self)); __MCStringChanged(self, false, false, t_can_be_native); // We succeeded. return true; } MC_DLLEXPORT_DEF bool MCStringPrependNativeChar(MCStringRef self, char_t p_char) { return MCStringPrependNativeChars(self, &p_char, 1); } MC_DLLEXPORT_DEF bool MCStringPrependChar(MCStringRef self, unichar_t p_char) { return MCStringPrependChars(self, &p_char, 1); } MC_DLLEXPORT_DEF bool MCStringPrependCodepoint (MCStringRef self, codepoint_t p_codepoint) { uindex_t t_num_units; unichar_t t_units[2]; t_num_units = MCStringCodepointToSurrogates (p_codepoint, t_units); return MCStringPrependChars (self, t_units, t_num_units); } MC_DLLEXPORT_DEF bool MCStringInsert(MCStringRef self, uindex_t p_at, MCStringRef p_substring) { MCAssert(MCStringIsMutable(self)); if (__MCStringIsIndirect(p_substring)) p_substring = p_substring -> string; // Only do the insert now if self != substring. if (self != p_substring) { if (__MCStringIsNative(p_substring)) return MCStringInsertNativeChars(self, p_at, p_substring -> native_chars, p_substring -> char_count); return MCStringInsertChars(self, p_at, p_substring -> chars, p_substring -> char_count); } // Otherwise copy and recurse. MCAutoStringRef t_substring_copy; MCStringCopy(p_substring, &t_substring_copy); return MCStringInsert(self, p_at, *t_substring_copy); } MC_DLLEXPORT_DEF bool MCStringInsertSubstring(MCStringRef self, uindex_t p_at, MCStringRef p_substring, MCRange p_range) { MCAssert(MCStringIsMutable(self)); if (__MCStringIsIndirect(p_substring)) p_substring = p_substring -> string; // Only do the insert now if self != substring. if (self != p_substring) { if (__MCStringIsNative(p_substring)) return MCStringInsertNativeChars(self, p_at, p_substring -> native_chars + p_range . offset, p_range . length); return MCStringInsertChars(self, p_at, p_substring -> chars + p_range . offset, p_range . length); } // Otherwise copy substring and insert. MCAutoStringRef t_substring_substring; return MCStringCopySubstring(p_substring, p_range, &t_substring_substring) && MCStringInsert(self, p_at, *t_substring_substring); } MC_DLLEXPORT_DEF bool MCStringInsertNativeChars(MCStringRef self, uindex_t p_at, const char_t *p_chars, uindex_t p_char_count) { MCAssert(MCStringIsMutable(self)); // Ensure the string is not indirect. if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; p_at = MCMin(p_at, self -> char_count); // Ensure we have enough room in self - with the gap at p_at. if (!__MCStringExpandAt(self, p_at, p_char_count)) return false; if (__MCStringIsNative(self)) { MCMemoryCopy(self -> native_chars + p_at, p_chars, p_char_count); __MCStringChanged(self, true, true, true); return true; } // Now copy the chars across. for(uindex_t i = 0; i < p_char_count; i++) self -> chars[p_at + i] = MCUnicodeCharMapFromNative(p_chars[i]); // Inserting native chars cannot change simple status __MCStringChanged(self); // We succeeded. return true; } MC_DLLEXPORT_DEF bool MCStringInsertChars(MCStringRef self, uindex_t p_at, const unichar_t *p_chars, uindex_t p_char_count) { MCAssert(MCStringIsMutable(self)); // Ensure the string is not indirect. if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; p_at = MCMin(p_at, self -> char_count); // Ensure we have enough room in self - with the gap at the p_at. if (!__MCStringExpandAt(self, p_at, p_char_count)) return false; if (__MCStringIsNative(self)) { bool t_not_native; t_not_native = false; for(uindex_t i = 0; i < p_char_count; i++) if (!MCUnicodeCharMapToNative(p_chars[i], self -> native_chars[i + p_at])) { t_not_native = true; break; } if (!t_not_native) { self -> native_chars[self -> char_count] = '\0'; __MCStringChanged(self, true, true, true); return true; } __MCStringShrinkAt(self, p_at, p_char_count); if (!__MCStringUnnativize(self)) { return false; } return MCStringInsertChars(self, p_at, p_chars, p_char_count); } // Need to clamp p_at again in case number of codeunits has decreased. p_at = MCMin(p_at, self -> char_count); // Copy the chars across recomputing whether the string can be native at // the same time. bool t_can_be_native; t_can_be_native = __MCStringCopyChars(self -> chars + p_at, p_chars, p_char_count, __MCStringCanBeNative(self)); __MCStringChanged(self, false, false, t_can_be_native); // We succeeded. return true; } MC_DLLEXPORT_DEF bool MCStringInsertNativeChar(MCStringRef self, uindex_t p_at, char_t p_char) { return MCStringInsertNativeChars(self, p_at, &p_char, 1); } MC_DLLEXPORT_DEF bool MCStringInsertChar(MCStringRef self, uindex_t p_at, unichar_t p_char) { return MCStringInsertChars(self, p_at, &p_char, 1); } MC_DLLEXPORT_DEF bool MCStringInsertCodepoint (MCStringRef self, uindex_t p_at, codepoint_t p_codepoint) { uindex_t t_num_units; unichar_t t_units[2]; t_num_units = MCStringCodepointToSurrogates (p_codepoint, t_units); return MCStringInsertChars (self, p_at, t_units, t_num_units); } MC_DLLEXPORT_DEF bool MCStringRemove(MCStringRef self, MCRange p_range) { MCAssert(MCStringIsMutable(self)); // Ensure the string is not indirect. if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; __MCStringClampRange(self, p_range); // Copy down the chars above the string taking into account the implicit // NUL. __MCStringShrinkAt(self, p_range . offset, p_range . length); if (!__MCStringIsNative(self)) __MCStringChanged(self, false, false); else __MCStringChanged(self, true, true, true); // We succeeded. return true; } MC_DLLEXPORT_DEF bool MCStringSubstring(MCStringRef self, MCRange p_range) { MCAssert(MCStringIsMutable(self)); // Ensure the string is not indirect. if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; __MCStringClampRange(self, p_range); // Remove the surrounding chars. // On the left if necessary if (p_range . offset != 0) { __MCStringShrinkAt(self, 0, p_range . offset); p_range . offset = 0; } // And on the right if necessary if (p_range . offset + p_range . length != self -> char_count) __MCStringShrinkAt(self, p_range . length, self -> char_count - p_range . length); // We succeeded. return true; } bool MCStringReplaceNativeChars(MCStringRef self, MCRange p_range, const char_t *p_chars, uindex_t p_char_count) { MCAssert(MCStringIsMutable(self)); // Ensure the string is not indirect. if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; __MCStringClampRange(self, p_range); // Work out the new size of the string. uindex_t t_new_char_count; t_new_char_count = self -> char_count - p_range . length + p_char_count; if (t_new_char_count > self -> char_count) { // Expand the string at the end of the range by the amount extra we // need. if (!__MCStringExpandAt(self, p_range . offset + p_range . length, t_new_char_count - self -> char_count)) return false; } else if (t_new_char_count < self -> char_count) { // Shrink the last part of the range by the amount less we need. __MCStringShrinkAt(self, p_range . offset + (p_range . length - (self -> char_count - t_new_char_count)), (self -> char_count - t_new_char_count)); } if (__MCStringIsNative(self)) { // Copy across the replacement chars. MCMemoryCopy(self -> native_chars + p_range . offset, p_chars, p_char_count); __MCStringChanged(self, true, true, true); return true; } // Now copy the chars across. for(uindex_t i = 0; i < p_char_count; i++) self -> chars[i + p_range . offset] = MCUnicodeCharMapFromNative(p_chars[i]); __MCStringChanged(self); // We succeeded. return true; } bool MCStringReplaceChars(MCStringRef self, MCRange p_range, const unichar_t *p_chars, uindex_t p_char_count) { MCAssert(MCStringIsMutable(self)); // Ensure the string is not indirect. if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; __MCStringClampRange(self, p_range); // Work out the new size of the string. uindex_t t_new_char_count; t_new_char_count = self -> char_count - p_range . length + p_char_count; index_t t_change; t_change = t_new_char_count - self -> char_count; if (t_change > 0) { // Expand the string at the end of the range by the amount extra we // need. if (!__MCStringExpandAt(self, p_range . offset + p_range . length, t_change)) return false; } else if (t_change < 0) { // Shrink the last part of the range by the amount less we need. __MCStringShrinkAt(self, p_range . offset + (p_range . length + t_change), -t_change); } if (__MCStringIsNative(self)) { bool t_not_native; t_not_native = false; for(uindex_t i = 0; i < p_char_count; i++) if (!MCUnicodeCharMapToNative(p_chars[i], self -> native_chars[i + p_range . offset])) { t_not_native = true; break; } if (!t_not_native) { self -> native_chars[self -> char_count] = '\0'; __MCStringChanged(self, true, true, true); return true; } if (t_change > 0) __MCStringShrinkAt(self, p_range . offset + p_range . length, t_change); else if (t_change < 0) { if (!__MCStringExpandAt(self, p_range . offset + (p_range . length + t_change), -t_change)) return false; } if (!__MCStringUnnativize(self)) { return false; } return MCStringReplaceChars(self, p_range, p_chars, p_char_count); } // Need to clamp range again in case the number of codeunits has decreased. __MCStringClampRange(self, p_range); // Copy the chars across recomputing whether the string can be native at // the same time. bool t_can_be_native; t_can_be_native = __MCStringCopyChars(self -> chars + p_range . offset, p_chars, p_char_count, __MCStringCanBeNative(self)); __MCStringChanged(self, false, false, t_can_be_native); // We succeeded. return true; } MC_DLLEXPORT_DEF bool MCStringReplace(MCStringRef self, MCRange p_range, MCStringRef p_replacement) { __MCAssertIsMutableString(self); if (__MCStringIsIndirect(p_replacement)) p_replacement = p_replacement -> string; // Only do the replace now if self != substring. if (self != p_replacement) { if (__MCStringIsNative(p_replacement)) return MCStringReplaceNativeChars(self, p_range, p_replacement -> native_chars, p_replacement -> char_count); return MCStringReplaceChars(self, p_range, p_replacement -> chars, p_replacement -> char_count); } // Otherwise copy and recurse. MCAutoStringRef t_replacement_copy; MCStringCopy(p_replacement, &t_replacement_copy); return MCStringReplace(self, p_range, *t_replacement_copy); } MC_DLLEXPORT_DEF bool MCStringPad(MCStringRef self, uindex_t p_at, uindex_t p_count, MCStringRef p_value) { __MCAssertIsMutableString(self); // Ensure the string is not indirect. if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; if (__MCStringIsIndirect(p_value)) p_value = p_value -> string; if (!__MCStringExpandAt(self, p_at, p_count * (p_value != nil ? p_value -> char_count : 1))) return false; if (p_value != nil) for(uindex_t i = 0; i < p_count; i++) MCMemoryCopy(self -> chars + p_at + i * p_value -> char_count, p_value -> chars, p_value -> char_count * sizeof(strchar_t)); __MCStringChanged(self); return true; } MC_DLLEXPORT_DEF bool MCStringResolvesLeftToRight(MCStringRef self) { __MCAssertIsString(self); if (MCStringIsNative(self) || MCStringCanBeNative(self)) return true; return MCBidiFirstStrongIsolate(self, 0) == 0; } //////////////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF bool MCStringAppendFormat(MCStringRef self, const char *p_format, ...) { bool t_success; va_list t_args; va_start(t_args, p_format); t_success = MCStringAppendFormatV(self, p_format, t_args); va_end(t_args); return t_success; } MC_DLLEXPORT_DEF bool MCStringAppendFormatV(MCStringRef self, const char *p_format, va_list p_args) { __MCAssertIsMutableString(self); MCAutoStringRef t_formatted_string; if (!MCStringFormatV(&t_formatted_string, p_format, p_args)) return false; return MCStringAppend(self, *t_formatted_string); } //////////////////////////////////////////////////////////////////////////////// static void split_find_end_of_element_native(const char_t *sptr, const char_t *eptr, const char_t *del, uindex_t p_del_length, const char_t*& r_end_ptr, MCStringOptions p_options) { /* Empty delimiters are never found */ if (0 == p_del_length) { r_end_ptr = eptr; return; } while(sptr < eptr - p_del_length + 1) { // Compute the length of the shared prefix at the current offset. size_t t_prefix_length; t_prefix_length = __MCNativeOp_SharedPrefix(sptr, (size_t)(eptr - sptr), del, p_del_length, p_options); if (t_prefix_length == p_del_length) { r_end_ptr = sptr; return; } sptr += 1; } r_end_ptr = eptr; } static void split_find_end_of_element_and_key_native(const char_t *sptr, const char_t *eptr, const char_t *del, uindex_t p_del_length, const char_t *key, uindex_t p_key_length, const char_t*& r_key_ptr, const char_t *& r_end_ptr, MCStringOptions p_options) { /* Empty delimiters are never found */ if (0 == p_key_length) { split_find_end_of_element_native(sptr, eptr, del, p_del_length, r_end_ptr, p_options); r_key_ptr = r_end_ptr; return; } while(sptr < eptr - p_key_length + 1) { // Compute the length of the shared prefix at the current offset. uindex_t t_prefix_length; t_prefix_length = __MCNativeOp_SharedPrefix(sptr, (size_t)(eptr - sptr), key, p_key_length, p_options); if (t_prefix_length == p_key_length) { r_key_ptr = sptr; break; } if (0 < p_del_length && sptr < eptr - p_del_length + 1) { t_prefix_length = __MCNativeOp_SharedPrefix(sptr, (size_t)(eptr - sptr), del, p_del_length, p_options); if (t_prefix_length == p_del_length) { r_key_ptr = r_end_ptr = sptr; return; } } sptr += 1; } // key not found if (sptr == eptr - p_key_length + 1) { r_key_ptr = eptr; r_end_ptr = eptr; } split_find_end_of_element_native(sptr, eptr, del, p_del_length, r_end_ptr, p_options); } bool MCStringSplitNative(MCStringRef self, MCStringRef p_elem_del, MCStringRef p_key_del, MCStringOptions p_options, MCArrayRef& r_array) { MCAutoArrayRef t_array; if (!MCArrayCreateMutable(&t_array)) return false; if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsIndirect(p_elem_del)) p_elem_del = p_elem_del -> string; const char_t *t_sptr; const char_t *t_eptr; t_sptr = self -> native_chars; t_eptr = self -> native_chars + self -> char_count; if (p_key_del == nil) { uindex_t t_index; t_index = 1; for(;;) { const char_t *t_element_end = NULL; split_find_end_of_element_native(t_sptr, t_eptr, p_elem_del -> native_chars, p_elem_del -> char_count, t_element_end, p_options); MCAutoStringRef t_string; if (!MCStringCreateWithNativeChars(t_sptr, t_element_end - t_sptr, &t_string)) return false; if (!MCArrayStoreValueAtIndex(*t_array, t_index, *t_string)) return false; if (t_element_end + p_elem_del -> char_count >= t_eptr) break; t_index += 1; t_sptr = t_element_end + p_elem_del -> char_count; } } else { if (__MCStringIsIndirect(p_key_del)) p_elem_del = p_key_del -> string; for(;;) { const char_t *t_element_end = NULL; const char_t *t_key_end = NULL; split_find_end_of_element_and_key_native(t_sptr, t_eptr, p_elem_del -> native_chars, p_elem_del -> char_count, p_key_del -> native_chars, p_key_del -> char_count, t_key_end, t_element_end, p_options); MCNewAutoNameRef t_name; if (!MCNameCreateWithNativeChars(t_sptr, t_key_end - t_sptr, &t_name)) return false; if (t_key_end char_count) t_key_end += p_key_del -> char_count; MCAutoStringRef t_string; if (!MCStringCreateWithNativeChars(t_key_end, t_element_end - t_key_end, &t_string)) return false; if (!MCArrayStoreValue(*t_array, true, *t_name, *t_string)) return false; if (t_element_end + p_elem_del -> char_count >= t_eptr) break; t_sptr = t_element_end + p_elem_del -> char_count; } } if (!MCArrayCopy(*t_array, r_array)) return false; return true; } bool MCStringFindAndReplaceChar(MCStringRef self, char_t p_pattern, char_t p_replacement, MCStringOptions p_options) { __MCAssertIsMutableString(self); // Ensure the string is not indirect if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; if (p_options == kMCStringOptionCompareExact || p_options == kMCStringOptionCompareNonliteral) { // Simplest case, just substitute pattern for replacement. for(uindex_t i = 0; i < self -> char_count; i++) if (self -> native_chars[i] == p_pattern) self -> native_chars[i] = p_replacement; } else { char_t t_from; t_from = __MCNativeChar_Fold(p_pattern); // Now substitute pattern for replacement, taking making sure its a caseless compare. for(uindex_t i = 0; i < self -> char_count; i++) if (__MCNativeChar_Fold(self -> native_chars[i]) == t_from) self -> native_chars[i] = p_replacement; } return true; } bool MCStringFindAndReplaceNative(MCStringRef self, MCStringRef p_pattern, MCStringRef p_replacement, MCStringOptions p_options) { __MCAssertIsMutableString(self); // Ensure the string is not indirect. if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; if (__MCStringIsIndirect(p_replacement)) p_replacement = p_replacement -> string; if (__MCStringIsIndirect(p_pattern)) p_pattern = p_pattern -> string; if (p_pattern -> char_count == 1 && p_replacement -> char_count == 1) return MCStringFindAndReplaceChar(self, p_pattern -> native_chars[0], p_replacement -> native_chars[0], p_options); if (self -> char_count != 0) { char_t *t_output; uindex_t t_output_length; uindex_t t_output_capacity; uindex_t t_offset; t_output = nil; t_output_length = 0; t_output_capacity = 0; t_offset = 0; for(;;) { // Search for the next occurence of from in whole. uindex_t t_next_offset; bool t_found; // AL-2014-05-23: [[ Bug 12482 ]] Pass through string options themselves, rather than a bool. t_found = MCStringFirstIndexOf(self, p_pattern, t_offset, p_options, t_next_offset); // If we found an instance of from, then we need space for to; otherwise, // we update the offset, and need just room up to it. uindex_t t_space_needed; if (t_found) t_space_needed = (t_next_offset - t_offset) + p_replacement -> char_count; else { t_next_offset = self -> char_count; t_space_needed = t_next_offset - t_offset; } // Expand the buffer as necessary. // MW-2015-05-26: [[ Bug 15352 ]] Allocate more memory if (t_output_length + t_space_needed + 1 > t_output_capacity) { if (t_output_capacity == 0) t_output_capacity = 4096; while(t_output_length + t_space_needed + 1 > t_output_capacity) t_output_capacity *= 2; if (!MCMemoryReallocate(t_output, t_output_capacity, t_output)) { MCMemoryDeallocate(t_output); return false; } } // Copy in self, up to the offset. memcpy(t_output + t_output_length, self -> native_chars + t_offset, t_next_offset - t_offset); t_output_length += t_next_offset - t_offset; // No more occurences were found, so we are done. if (!t_found) break; // Now copy in replacement. memcpy(t_output + t_output_length, p_replacement -> native_chars, p_replacement -> char_count); t_output_length += p_replacement -> char_count; // Update offset t_offset = t_next_offset + p_pattern -> char_count; } // Add the implicit NUL t_output[t_output_length] = '\0'; MCMemoryDeleteArray(self -> native_chars); self -> native_chars = t_output; self -> char_count = t_output_length; self -> capacity = t_output_capacity; } __MCStringChanged(self, true, true, true); return true; } //////////////////////////////////////////////////////////////////////////////// static void split_find_end_of_element(const void *sptr, uindex_t length, bool native, const void* p_del, uindex_t p_del_length, bool p_del_native, MCStringOptions p_options, uindex_t& r_end_offset, uindex_t& r_found_length) { bool t_found; MCRange t_found_range; t_found = MCUnicodeFind(sptr, length, native, p_del, p_del_length, p_del_native, (MCUnicodeCompareOption)p_options, t_found_range); if (!t_found) { r_end_offset = length; r_found_length = 0; return; } r_end_offset = t_found_range . offset; r_found_length = t_found_range . length; } static void split_find_end_of_element_and_key(const void *sptr, uindex_t length, bool native, const void *p_del, uindex_t p_del_length, bool p_del_native, const void *p_key, uindex_t p_key_length, bool p_key_native, MCStringOptions p_options, uindex_t& r_key_end, uindex_t& r_element_end, uindex_t& r_del_found_length, uindex_t& r_key_found_length) { // Not as fast as it could be... bool t_key_found, t_del_found; MCRange t_key_found_range, t_del_found_range; t_del_found = MCUnicodeFind(sptr, length, native, p_del, p_del_length, p_del_native, (MCUnicodeCompareOption)p_options, t_del_found_range); // SN-2014-07-29: [[ Bug 13018 ]] Use t_key_found_range for the key, not t_del_found_range t_key_found = MCUnicodeFind(sptr, length, native, p_key, p_key_length, p_key_native, (MCUnicodeCompareOption)p_options, t_key_found_range); if (!t_del_found) { r_element_end = length; r_del_found_length = 0; } else { r_element_end = t_del_found_range.offset; r_del_found_length = t_del_found_range.length; } /* Deal with the possibility that the delimiter was found before the key */ if (!t_key_found || r_element_end < t_key_found_range.offset) { r_key_end = r_element_end; r_key_found_length = 0; } else { r_key_end = t_key_found_range.offset; r_key_found_length = t_key_found_range.length; } } MC_DLLEXPORT_DEF bool MCStringSplit(MCStringRef self, MCStringRef p_elem_del, MCStringRef p_key_del, MCStringOptions p_options, MCArrayRef& r_array) { __MCAssertIsString(self); __MCAssertIsString(p_elem_del); if (nil != p_key_del) __MCAssertIsString(p_key_del); if (__MCStringIsIndirect(self)) self = self -> string; // SN-2014-03-24: [[ SplitWithStrings ]] No longer checks whether the delimiter is actually 1-char long. if (self -> char_count == 0) { r_array = MCValueRetain(kMCEmptyArray); return true; } if (__MCStringIsNative(self)) { if (MCStringIsNative(p_elem_del) && (p_key_del == nil || MCStringIsNative(p_key_del))) return MCStringSplitNative(self, p_elem_del, p_key_del, p_options, r_array); } MCAutoArrayRef t_array; if (!MCArrayCreateMutable(&t_array)) return false; if (__MCStringIsIndirect(p_elem_del)) p_elem_del = p_elem_del -> string; const void *t_echar, *t_kchar; bool del_native, key_native; del_native = __MCStringIsNative(p_elem_del); t_echar = p_elem_del -> chars; if (p_key_del != nil) { if (__MCStringIsIndirect(p_key_del)) p_key_del = p_key_del -> string; key_native = __MCStringIsNative(p_key_del); t_kchar = p_key_del -> chars; } const void *t_sptr; bool self_native = __MCStringIsNative(self); uindex_t t_del_length = __MCStringGetLength(p_elem_del); if (self_native) t_sptr = self -> native_chars; else t_sptr = self -> chars; uindex_t t_offset, t_to_end; t_to_end = self -> char_count; t_offset = 0; if (p_key_del == nil) { uindex_t t_index; t_index = 1; for(;;) { uindex_t t_found_del_length, t_end_offset; split_find_end_of_element(t_sptr, t_to_end, self_native, t_echar, t_del_length, del_native, p_options, t_end_offset, t_found_del_length); MCAutoStringRef t_string; if (!MCStringCopySubstring(self, MCRangeMake(t_offset, t_end_offset), &t_string)) return false; if (!MCArrayStoreValueAtIndex(*t_array, t_index, *t_string)) return false; if (t_end_offset + t_found_del_length >= t_to_end) break; t_index += 1; t_offset += t_end_offset + t_found_del_length; t_sptr = (const char *)t_sptr + (self_native ? t_end_offset + t_found_del_length : 2 * (t_end_offset + t_found_del_length)); t_to_end -= (t_end_offset + t_found_del_length); } } else { uindex_t t_key_length = __MCStringGetLength(p_key_del); for(;;) { uindex_t t_found_del_length, t_found_key_length, t_key_end, t_element_end; split_find_end_of_element_and_key(t_sptr, t_to_end, self_native, t_echar, t_del_length, del_native, t_kchar, t_key_length, key_native, p_options, t_key_end, t_element_end, t_found_del_length, t_found_key_length); MCAutoStringRef t_key_string; if (!MCStringCopySubstring(self, MCRangeMake(t_offset, t_key_end), &t_key_string)) return false; MCNewAutoNameRef t_key_name; if (!MCNameCreate(*t_key_string, &t_key_name)) return false; if (t_key_end != t_element_end) t_key_end += t_found_key_length; MCAutoStringRef t_string; if (!MCStringCopySubstring(self, MCRangeMake(t_offset + t_key_end, t_element_end - t_key_end), &t_string)) return false; if (!MCArrayStoreValue(*t_array, true, *t_key_name, *t_string)) return false; if (t_element_end + t_found_del_length >= t_to_end) break; t_offset += t_element_end + t_found_del_length; t_sptr = (const char *)t_sptr + (self_native ? t_element_end + t_found_del_length : 2 * (t_element_end + t_found_del_length)); t_to_end -= (t_element_end + t_found_del_length); } } if (!MCArrayCopy(*t_array, r_array)) return false; return true; } MC_DLLEXPORT_DEF bool MCStringSplitByDelimiter(MCStringRef self, MCStringRef p_elem_del, MCStringOptions p_options, MCProperListRef& r_list) { __MCAssertIsString(self); __MCAssertIsString(p_elem_del); if (__MCStringIsIndirect(self)) self = self -> string; // SN-2014-03-24: [[ SplitWithStrings ]] No longer checks whether the delimiter is actually 1-char long. if (self -> char_count == 0) { r_list = MCValueRetain(kMCEmptyProperList); return true; } if (MCStringIsNative(self)) { if (MCStringIsNative(p_elem_del)) return MCStringSplitByDelimiterNative(self, p_elem_del, p_options, r_list); } if (__MCStringIsIndirect(p_elem_del)) p_elem_del = p_elem_del -> string; const void *t_echar; bool del_native; del_native = MCStringIsNative(p_elem_del); t_echar = p_elem_del -> chars; const void *t_sptr; bool self_native = MCStringIsNative(self); uindex_t t_del_length = MCStringGetLength(p_elem_del); if (self_native) t_sptr = self -> native_chars; else t_sptr = self -> chars; uindex_t t_offset, t_to_end; t_to_end = self -> char_count; t_offset = 0; bool t_success; t_success = true; MCAutoStringRefArray t_strings; for(;;) { uindex_t t_found_del_length, t_end_offset; split_find_end_of_element(t_sptr, t_to_end, self_native, t_echar, t_del_length, del_native, p_options, t_end_offset, t_found_del_length); MCAutoStringRef t_string; if (!MCStringCopySubstring(self, MCRangeMake(t_offset, t_end_offset), &t_string)) { return false; } if (!t_strings.Push(*t_string)) { return false; } if (t_end_offset + t_found_del_length >= t_to_end) break; t_offset += t_end_offset + t_found_del_length; t_sptr = (const char *)t_sptr + (self_native ? t_end_offset + t_found_del_length : 2 * (t_end_offset + t_found_del_length)); t_to_end -= (t_end_offset + t_found_del_length); } return t_strings.TakeAsProperList(r_list); } static bool MCStringSplitByDelimiterNative(MCStringRef self, MCStringRef p_elem_del, MCStringOptions p_options, MCProperListRef& r_list) { if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsIndirect(p_elem_del)) p_elem_del = p_elem_del -> string; const char_t *t_sptr; const char_t *t_eptr; t_sptr = self -> native_chars; t_eptr = self -> native_chars + self -> char_count; bool t_success; t_success = true; MCAutoStringRefArray t_strings; for(;;) { const char_t *t_element_end = NULL; split_find_end_of_element_native(t_sptr, t_eptr, p_elem_del -> native_chars, p_elem_del -> char_count, t_element_end, p_options); MCAutoStringRef t_string; if (!MCStringCreateWithNativeChars(t_sptr, t_element_end - t_sptr, &t_string)) { return false; } if (!t_strings.Push(*t_string)) { return false; } if (t_element_end + p_elem_del -> char_count >= t_eptr) break; if (!t_success) break; t_sptr = t_element_end + p_elem_del -> char_count; } return t_strings.TakeAsProperList(r_list); } MC_DLLEXPORT_DEF bool MCStringFindAndReplaceChar(MCStringRef self, codepoint_t p_pattern, codepoint_t p_replacement, MCStringOptions p_options) { __MCAssertIsMutableString(self); // Ensure the string is not indirect. if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; // Can the replacement be done in-place? Reasons it might not be possible: // // (x) The UTF-16 encoding of the codepoints are different lengths // (x) Normalisation is required and one or both codepoints are composed // (x) Case folding is required and the case mapping is not simple // //This can only be true if both // codepoints have the same length when encoded. It also isn't possible if // normalisation is required and one or both codepoints are composed. Another if ( // Either character is outside the BMP (p_pattern > UNICHAR_MAX || p_replacement > UNICHAR_MAX) // Normalisation or case-folding has been requested || (p_options != kMCStringOptionCompareExact) ) { // Do it via the slow-path full string replacement MCAutoStringRef t_pattern, t_replacement; unichar_t t_buffer[2]; /* UNCHECKED */ MCStringCreateWithChars(t_buffer, MCStringCodepointToSurrogates(p_pattern, t_buffer), &t_pattern); /* UNCHECKED */ MCStringCreateWithChars(t_buffer, MCStringCodepointToSurrogates(p_replacement, t_buffer), &t_replacement); return MCStringFindAndReplace(self, *t_pattern, *t_replacement, p_options); } bool t_native = __MCStringIsNative(self); // The options must be kMCStringOptionCompareExact for (uindex_t i = 0; i < self -> char_count; i++) { if (t_native) { if (self -> native_chars[i] == p_pattern) self -> native_chars[i] = p_replacement; } else { if (self -> chars[i] == p_pattern) self -> chars[i] = p_replacement; } } return true; } MC_DLLEXPORT_DEF bool MCStringFindAndReplace(MCStringRef self, MCStringRef p_pattern, MCStringRef p_replacement, MCStringOptions p_options) { __MCAssertIsMutableString(self); __MCAssertIsString(p_pattern); __MCAssertIsString(p_replacement); // Ensure the string is not indirect. if (__MCStringIsIndirect(self)) if (!__MCStringResolveIndirect(self)) return false; if (__MCStringIsNative(self)) { if (MCStringIsNative(p_pattern)) { if (MCStringIsNative(p_replacement)) return MCStringFindAndReplaceNative(self, p_pattern, p_replacement, p_options); } else if (MCStringCantBeEqualToNative(p_pattern, p_options)) return true; } if (!__MCStringUnnativize(self)) { return false; } if (self -> char_count != 0) { strchar_t *t_output; uindex_t t_output_length; uindex_t t_output_capacity; t_output = nil; t_output_length = 0; t_output_capacity = 0; MCRange t_range; t_range . offset = 0; t_range . length = self -> char_count; for(;;) { MCRange t_found_range; // Search for the next occurence of from in whole. bool t_found; t_found = MCStringFind(self, t_range, p_pattern, p_options, &t_found_range); // If we found an instance of from, then we need space for to; otherwise, // we update the offset, and need just room up to it. uindex_t t_space_needed; if (t_found) t_space_needed = (t_found_range.offset - t_range.offset) + p_replacement -> char_count; else { t_found_range.offset = self -> char_count; t_space_needed = t_found_range.offset - t_range.offset; } // Expand the buffer as necessary. // MW-2015-05-26: [[ Bug 15352 ]] Allocate more memory if (t_output_length + t_space_needed + 1 > t_output_capacity) { if (t_output_capacity == 0) t_output_capacity = 4096; while(t_output_length + t_space_needed + 1 > t_output_capacity) t_output_capacity *= 2; if (!MCMemoryReallocate(t_output, t_output_capacity * sizeof(strchar_t), t_output)) { MCMemoryDeallocate(t_output); return false; } } // Copy in self, up to the offset. memcpy(t_output + t_output_length, self -> chars + t_range.offset, (t_found_range.offset - t_range.offset) * sizeof(strchar_t)); t_output_length += t_found_range.offset - t_range.offset; // No more occurences were found, so we are done. if (!t_found) break; // Now copy in replacement. if (MCStringIsNative(p_replacement)) MCUnicodeCharsMapFromNative(p_replacement -> native_chars, p_replacement -> char_count, t_output + t_output_length); else memcpy(t_output + t_output_length, p_replacement -> chars, p_replacement -> char_count * sizeof(strchar_t)); t_output_length += p_replacement -> char_count; // Update offset t_range.offset = t_found_range.offset + t_found_range.length; if (t_range.offset >= self -> char_count) break; } // Add the implicit NUL t_output[t_output_length] = '\0'; MCMemoryDeleteArray(self -> chars); self -> chars = t_output; self -> char_count = t_output_length; self -> capacity = t_output_capacity; __MCStringChanged(self, false, false, __MCStringCanBeNative(self) && MCStringCanBeNative(p_replacement)); } return true; } MC_DLLEXPORT_DEF bool MCStringWildcardMatch(MCStringRef source, MCRange source_range, MCStringRef pattern, MCStringOptions p_options) { __MCAssertIsString(source); __MCAssertIsString(pattern); bool source_native = MCStringIsNative(source); const void *source_chars; if (source_native) source_chars = source -> native_chars + source_range . offset; else source_chars = source -> chars + source_range . offset; return MCUnicodeWildcardMatch(source_chars, source_range . length, source_native, pattern -> chars, pattern -> char_count, MCStringIsNative(pattern), (MCUnicodeCompareOption)p_options); } //////////////////////////////////////////////////////////////////////////////// void __MCStringDestroy(__MCString *self) { if (__MCStringIsIndirect(self)) { MCValueRelease(self -> string); } else { if (__MCStringIsNative(self)) MCMemoryDeleteArray(self -> native_chars); else MCMemoryDeleteArray(self -> chars); } } bool __MCStringCopyDescription(__MCString *self, MCStringRef& r_desc) { return MCStringFormat(r_desc, "\"%@\"", self); } hash_t __MCStringHash(__MCString *self) { return MCStringHash(self, kMCStringOptionCompareExact); } bool __MCStringIsEqualTo(__MCString *self, __MCString *p_other_self) { return MCStringIsEqualTo(self, p_other_self, kMCStringOptionCompareExact); } bool __MCStringImmutableCopy(__MCString* self, bool p_release, __MCString*& r_immutable_value) { if (!p_release) return MCStringCopy(self, r_immutable_value); return MCStringCopyAndRelease(self, r_immutable_value); } //////////////////////////////////////////////////////////////////////////////// static void __MCStringClampRange(MCStringRef self, MCRange& x_range) { uindex_t t_left, t_right; t_left = MCMin(x_range . offset, self -> char_count); t_right = MCMin(x_range . offset + MCMin(x_range . length, UINDEX_MAX - x_range . offset), self -> char_count); x_range . offset = t_left; x_range . length = t_right - t_left; } /*static uindex_t *__MCStringGetCapacityPtr(MCStringRef self) { // In mutable strings, the capacity is stored after the chars at the // first aligned location. uindex_t *t_capacity_ptr; t_capacity_ptr = (uindex_t *)(self -> chars + ((self -> char_count + sizeof(uindex_t) - 1) & ~(sizeof(uindex_t) - 1))); return t_capacity_ptr; }*/ static uindex_t __MCStringGetCapacity(MCStringRef self) { return self -> capacity; /*if (self -> chars != nil) return *(__MCStringGetCapacityPtr(self)); // An uninitialized string (or one that has had its buffer deleted) will // have no chars ptr, so just return 0. return 0;*/ } static bool __MCStringExpandAt(MCStringRef self, uindex_t p_at, uindex_t p_count) { MCAssert(!__MCStringIsIndirect(self)); // Fetch the capacity. uindex_t t_capacity; t_capacity = __MCStringGetCapacity(self); /* Overflow check. A string's length needs to fit into an * index_t, because sometimes negative indices are used. Ensure that the * length of the expanded string (including a null byte) fits. */ if (INDEX_MAX == p_count || INDEX_MAX - p_count - 1 < self->char_count) { return MCErrorThrowOutOfMemory(); } /* Overflow check. A string's total storage size must fit into a * size_t. The maximum capacity (i.e. number of chars) depends on whether it's a * Unicode (multibyte) or native (unibyte) string representation. */ size_t t_capacity_limit = SIZE_MAX / (__MCStringIsNative(self) ? sizeof(char_t) : sizeof(unichar_t)); if (t_capacity_limit == p_count || t_capacity_limit - p_count - 1 < self->char_count) { return MCErrorThrowOutOfMemory(); } // The capacity field stores the total number of chars that could fit not // including the implicit NUL, so if we fit, we can fast-track. if (t_capacity != 0 && self -> char_count + p_count native_chars + p_at + p_count, self -> native_chars + p_at, ((self -> char_count + 1) - p_at)); else MCMemoryMove(self -> chars + p_at + p_count, self -> chars + p_at, ((self -> char_count + 1) - p_at) * sizeof(unichar_t)); // Increase the char_count. self -> char_count += p_count; // Rewrite the capacity. //*(__MCStringGetCapacityPtr(self)) = t_capacity; // We succeeded. return true; } // If we get here then we need to reallocate first. // Base capacity - current length + inserted length + implicit NUL. uindex_t t_new_capacity; t_new_capacity = self -> char_count + p_count + 1; // Capacity rounded up to nearest aligned boundary and then space for // the capacity field. //t_new_capacity = ((t_new_capacity + sizeof(uindex_t) - 1) & (~(sizeof(uindex_t) - 1))) + sizeof(uindex_t); // Capacity rounded up to a suitable boundary (at some point this should // be a function of the string's size). t_new_capacity = (t_new_capacity + 63) & ~63; // Reallocate and shift up the chars above - including the implicit NUL. if (__MCStringIsNative(self)) { if (!MCMemoryReallocate(self -> native_chars, t_new_capacity, self -> native_chars)) return false; MCMemoryMove(self -> native_chars + p_at + p_count, self -> native_chars + p_at, ((self -> char_count + 1) - p_at)); } else { if (!MCMemoryReallocate(self -> chars, t_new_capacity * sizeof(unichar_t), self -> chars)) return false; MCMemoryMove(self -> chars + p_at + p_count, self -> chars + p_at, ((self -> char_count + 1) - p_at) * sizeof(unichar_t)); } // Increase the char_count. self -> char_count += p_count; // Update the capacity - notice that we shave off the capacity field itself // since it represents the char capacity rather than the size of the alloc'd // block. We also shave off room for the implicit NUL. self -> capacity = t_new_capacity - 1; //*(__MCStringGetCapacityPtr(self)) = t_new_capacity - 1 - sizeof(uindex_t); // We succeeded. return true; } static void __MCStringShrinkAt(MCStringRef self, uindex_t p_at, uindex_t p_count) { MCAssert(!__MCStringIsIndirect(self)); // Shift the chars above 'at' down to remove 'count', remembering to include // the implicit NUL. if (__MCStringIsNative(self)) MCMemoryMove(self -> native_chars + p_at, self -> native_chars + (p_at + p_count), (self -> char_count - (p_at + p_count) + 1)); else MCMemoryMove(self -> chars + p_at, self -> chars + (p_at + p_count), (self -> char_count - (p_at + p_count) + 1) * sizeof(strchar_t)); // Now adjust the length of the string. self -> char_count -= p_count; // TODO: Shrink the buffer if its too big. } static bool __MCStringNativize(MCStringRef self) { uindex_t t_char_count; return __MCStringNativize(self, t_char_count); } static bool __MCStringNativize(MCStringRef self, uindex_t & r_char_count) { if (MCStringIsNative(self)) { r_char_count = self -> char_count; return true; } if (__MCStringIsIndirect(self) && !__MCStringResolveIndirect(self)) { return false; } bool t_not_native; t_not_native = false; MCAutoArray chars; if (!chars.New(self -> char_count + 1)) { return false; } for(uindex_t i = 0; i < self -> char_count; i++) if (!MCUnicodeCharMapToNative(self -> chars[i], chars[i])) { t_not_native = true; break; } if (!t_not_native) { uindex_t t_ignored; MCMemoryDeleteArray(self -> chars); chars.Take(self -> native_chars, t_ignored); __MCStringChanged(self, true, true, true); self -> flags &= ~kMCStringFlagIsNotNative; self -> native_chars[self -> char_count] = '\0'; r_char_count = self -> char_count; return true; } // The string needs to be normalised before conversion to native characters. // All the native character sets we support use pre-composed characters. MCAutoStringRef t_norm; if (!MCStringNormalizedCopyNFC(self, &t_norm)) { return false; } // Nativisation is done on a char (grapheme) basis so we need to know the // number of graphemes in the string MCRange t_cu_range, t_char_range; t_cu_range = MCRangeMake(0, (*t_norm) -> char_count); if (!MCStringUnmapIndices(*t_norm, kMCCharChunkTypeGrapheme, t_cu_range, t_char_range)) { return false; } t_cu_range = MCRangeMake(0, (*t_norm) -> char_count); // Go through the graphemes of the string uindex_t t_current = 0, t_next; for (uindex_t i = 0; i < t_char_range . length; i++) { // If we've reached the end, set the next boundary manually t_next = MCStringGraphemeBreakIteratorAdvance(self, t_current); if (t_next == kMCLocaleBreakIteratorDone) t_next = self -> char_count; // All nativisable characters are 1 codeunit in length. if (t_next != t_current + 1 || !MCUnicodeCharMapToNative((*t_norm) -> chars[t_current], chars[i])) { chars[i] = '?'; } // Advance t_current = t_next; } uindex_t t_ignored; MCMemoryDeleteArray(self -> chars); chars.Take(self -> native_chars, t_ignored); self -> native_chars[t_char_range.length] = '\0'; __MCStringChanged(self, true, true, true); self -> flags &= ~kMCStringFlagIsNotNative; self -> char_count = t_char_range . length; r_char_count = t_char_range . length; return true; } static bool __MCStringUnnativize(MCStringRef self) { if (!MCStringIsNative(self)) return true; if (__MCStringIsIndirect(self) && !__MCStringResolveIndirect(self)) { return false; } uindex_t t_capacity = self -> capacity != 0 ? self -> capacity : self -> char_count; uindex_t t_char_count = self -> char_count; unichar_t *chars; if (!MCMemoryNewArray(t_capacity + 1, chars)) { return false; } MCStrCharsMapFromNative(chars, self -> native_chars, t_char_count); MCMemoryDeleteArray(self -> native_chars); self -> chars = chars; self -> char_count = t_char_count; // Set the NUL char. self -> chars[t_char_count] = '\0'; __MCStringChanged(self, true, true, true); self -> flags |= kMCStringFlagIsNotNative; return true; } static void __MCStringSetFlags(MCStringRef self, uindex_t basic, uindex_t trivial, uindex_t native) { MCAssert(!__MCStringIsIndirect(self)); if (native == kMCStringFlagSetTrue) { self -> flags |= kMCStringFlagCanBeNative; self -> flags |= kMCStringFlagIsTrivial; self -> flags |= kMCStringFlagIsBasic; return; } else if (native == kMCStringFlagSetFalse) { self -> flags &= ~kMCStringFlagCanBeNative; } if (trivial == kMCStringFlagSetTrue) { self -> flags |= kMCStringFlagIsTrivial; self -> flags |= kMCStringFlagIsBasic; return; } else if (trivial == kMCStringFlagSetFalse) { self -> flags &= ~kMCStringFlagIsTrivial; self -> flags &= ~kMCStringFlagCanBeNative; } if (basic == kMCStringFlagSetTrue) { self -> flags |= kMCStringFlagIsBasic; } else if (basic == kMCStringFlagSetFalse) { self -> flags &= ~kMCStringFlagIsBasic; self -> flags &= ~kMCStringFlagIsTrivial; self -> flags &= ~kMCStringFlagCanBeNative; } } static void __MCStringChanged(MCStringRef self, uindex_t basic, uindex_t trivial, uindex_t native) { MCAssert(!__MCStringIsIndirect(self)); self -> flags &= ~kMCStringFlagIsChecked; self -> flags &= ~kMCStringFlagHasNumber; __MCStringSetFlags(self, basic, trivial, native); } MC_DLLEXPORT_DEF codepoint_t MCStringSurrogatesToCodepoint(unichar_t p_lead, unichar_t p_trail) { return 0x10000 + ((p_lead & 0x3FF) UNICHAR_MAX) { p_codepoint -= 0x10000; r_units[0] = 0xD800 + (p_codepoint >> 10); r_units[1] = 0xDC00 + (p_codepoint & 0x3FF); return 2; } else { r_units[0] = p_codepoint; return 1; } } MC_DLLEXPORT_DEF bool MCStringIsValidSurrogatePair(MCStringRef self, uindex_t p_index) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsNative(self)) return false; // Check that the string is long enough // (Double-checking here is due to possible unsigned wrapping) if (p_index >= self -> char_count || (p_index + 1) >= self -> char_count) return false; // Check for a valid leading surrogate unichar_t t_char; t_char = self -> chars[p_index]; if (t_char < 0xD800 || t_char > 0xDBFF) return false; // Check for a valid trailFing surrogate t_char = self -> chars[p_index + 1]; if (t_char < 0xDC00 || t_char > 0xDFFF) return false; // All the checks passed return true; } MC_DLLEXPORT_DEF bool MCStringIsGraphemeClusterBoundary(MCStringRef self, uindex_t p_index) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsTrivial(self)) return true; if (p_index == 0 || p_index >= self -> char_count) return true; // There is not a cluster boundary between the lead and trail of a surrogate if (MCStringIsValidSurrogatePair(self, p_index - 1)) return false; // Resolve the codepoints either side of the index codepoint_t t_left_codepoint, t_right_codepoint; uindex_t t_dummy; t_dummy = p_index; if (!__MCStringFetchCodepointBefore(self, t_dummy, t_left_codepoint)) return true; t_dummy = p_index; if (!__MCStringFetchCodepointAfter(self, t_dummy, t_right_codepoint)) return true; return MCUnicodeIsGraphemeClusterBoundary(t_left_codepoint, t_right_codepoint); } //////////////////////////////////////////////////////////////////////////////// #if defined(__LINUX__) static const iconv_t k_invalid_iconv_fd = reinterpret_cast(-1); static bool is_valid_iconv_fd(iconv_t p_fd) { return p_fd != k_invalid_iconv_fd; } static iconv_t s_iconv_unicode_from_sys_fd = k_invalid_iconv_fd; static iconv_t s_iconv_unicode_to_sys_fd = k_invalid_iconv_fd; static iconv_t s_iconv_native_to_sys_fd = k_invalid_iconv_fd; static bool __MCStringInitializeIconv() { const char *k_internal_iconv_charset = (kMCByteOrderHost == kMCByteOrderLittleEndian ? "UTF-16LE" : "UTF-16BE"); // What is the system character encoding? // // Doing this here is unpleasant but the MCString*SysString functions are // needed before the libfoundation initialise call completes if (__MCSysCharset == nil) { setlocale(LC_CTYPE, ""); __MCSysCharset = nl_langinfo(CODESET); } s_iconv_unicode_from_sys_fd = iconv_open(k_internal_iconv_charset, __MCSysCharset); if (!is_valid_iconv_fd(s_iconv_unicode_from_sys_fd)) return false; s_iconv_unicode_to_sys_fd = iconv_open(__MCSysCharset, k_internal_iconv_charset); if (!is_valid_iconv_fd(s_iconv_unicode_to_sys_fd)) return false; s_iconv_native_to_sys_fd = iconv_open(__MCSysCharset, "ISO-8859-1"); if (!is_valid_iconv_fd(s_iconv_native_to_sys_fd)) return false; return true; } static void __MCStringFinalizeIconv() { auto t_iconv_cleanup = [](iconv_t& p_fd) { if (is_valid_iconv_fd(p_fd)) { iconv_close(p_fd); p_fd = k_invalid_iconv_fd; } }; t_iconv_cleanup(s_iconv_unicode_from_sys_fd); t_iconv_cleanup(s_iconv_unicode_to_sys_fd); t_iconv_cleanup(s_iconv_native_to_sys_fd); } #endif /* __LINUX__ */ MC_DLLEXPORT_DEF MCStringRef kMCEmptyString; MC_DLLEXPORT_DEF MCStringRef kMCTrueString; MC_DLLEXPORT_DEF MCStringRef kMCFalseString; MC_DLLEXPORT_DEF MCStringRef kMCMixedString; MC_DLLEXPORT_DEF MCStringRef kMCCommaString; MC_DLLEXPORT_DEF MCStringRef kMCLineEndString; MC_DLLEXPORT_DEF MCStringRef kMCTabString; MC_DLLEXPORT_DEF MCStringRef kMCNulString; bool __MCStringInitialize(void) { #if defined(__LINUX__) if (!__MCStringInitializeIconv()) return false; #endif if (!MCStringCreateWithNativeChars((const char_t *)"", 0, kMCEmptyString)) return false; if (!MCStringCreateWithNativeChars((const char_t *)"true", 4, kMCTrueString)) return false; if (!MCStringCreateWithNativeChars((const char_t *)"false", 5, kMCFalseString)) return false; if (!MCStringCreateWithNativeChars((const char_t *)"mixed", 5, kMCMixedString)) return false; if (!MCStringCreateWithNativeChars((const char_t *)",", 1, kMCCommaString)) return false; if (!MCStringCreateWithNativeChars((const char_t *)"\n", 1, kMCLineEndString)) return false; if (!MCStringCreateWithNativeChars((const char_t *)"\t", 1, kMCTabString)) return false; if (!MCStringCreateWithNativeChars((const char_t *)"\0", 1, kMCNulString)) return false; return true; } void __MCStringFinalize(void) { MCValueRelease(kMCFalseString); kMCFalseString = nil; MCValueRelease(kMCTrueString); kMCTrueString = nil; MCValueRelease(kMCEmptyString); kMCEmptyString = nil; MCValueRelease(kMCMixedString); kMCMixedString = nil; MCValueRelease(kMCCommaString); kMCCommaString = nil; MCValueRelease(kMCLineEndString); kMCLineEndString = nil; MCValueRelease(kMCTabString); kMCTabString = nil; MCValueRelease(kMCNulString); kMCNulString = nil; #if defined(__LINUX__) __MCStringFinalizeIconv(); #endif } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// #ifdef __LINUX__ #define ALLOC_PAD 64 static bool do_iconv(iconv_t fd, const char *in, size_t in_len, char * &out, size_t &out_len) { // Begin conversion. As a start, assume both encodings take the same // space. This is probably wrong but the array is grown as needed. size_t t_status = 0; size_t t_alloc_remain = 0; char * t_out; char * t_out_cursor; /* Reset the iconv file descriptor */ iconv(fd, nullptr, nullptr, nullptr, nullptr); t_out = (char*)malloc(in_len); if (t_out == nil) return false; t_alloc_remain = in_len; t_out_cursor = t_out; while (in_len > 0) { // Resize the destination array if it has been exhausted t_status = iconv(fd, (char**)&in, &in_len, &t_out_cursor, &t_alloc_remain); // Did the conversion fail? if (t_status == (size_t)-1) { // Insufficient output space errors can be fixed and retried if (errno == E2BIG) { // Increase the size of the output array uindex_t t_offset; t_offset = t_out_cursor - t_out; char *t_new_out = (char*)realloc(t_out, t_offset + t_alloc_remain + ALLOC_PAD); if (t_new_out == nil) { free(t_out); return false; } t_out = t_new_out; // Adjust the pointers because the output buffer may have moved t_out_cursor = t_out + t_offset; t_alloc_remain += ALLOC_PAD; // Remaining size, not total size // Try the conversion again continue; } else { // The error is one of the following: // EILSEQ - input byte invalid for input encoding // EINVAL - incomplete multibyte character at end of input // EBADF - invalid conversion file descriptor // None of these are recoverable so abort free(t_out); return false; } } else { // No error, conversion should be complete. MCAssert(in_len == 0); } } // Conversion has been completed out_len = t_out_cursor - t_out; out = t_out; return true; } MC_DLLEXPORT_DEF bool MCStringCreateWithSysString(const char *p_system_string, MCStringRef &r_string) { // Is the string empty? if (p_system_string == nil || *p_system_string == 0) { r_string = MCValueRetain(kMCEmptyString); return true; } // Was creation of the iconv FD successful? if (!is_valid_iconv_fd(s_iconv_unicode_from_sys_fd)) return false; // Measure the string size_t t_len; t_len = strlen(p_system_string); // Convert the string char *t_utf16_bytes; size_t t_utf16_byte_len; bool t_success; t_success = do_iconv(s_iconv_unicode_from_sys_fd, p_system_string, t_len, t_utf16_bytes, t_utf16_byte_len); if (!t_success) return false; // Create the StringRef MCStringRef t_string; t_success = MCStringCreateWithBytes((const byte_t*)t_utf16_bytes, t_utf16_byte_len, kMCStringEncodingUTF16, false, t_string); MCMemoryDeleteArray(t_utf16_bytes); if (!t_success) return false; r_string = t_string; return true; } MC_DLLEXPORT_DEF bool MCStringConvertToSysString(MCStringRef p_string, char *& r_system_string, size_t& r_byte_count) { // Create the pseudo-FD that iconv uses for character conversion. For // efficiency, convert straight from the internal format. iconv_t t_fd; const char *t_mc_string; size_t t_mc_len; // What is the system character encoding? // // Doing this here is unpleasant but the MCString*SysString functions are // needed before the libfoundation initialise call is made if (__MCSysCharset == nil) { setlocale(LC_CTYPE, ""); __MCSysCharset = nl_langinfo(CODESET); } if (MCStringIsNative(p_string) && MCStringGetNativeCharPtr(p_string) != nil) { t_fd = s_iconv_native_to_sys_fd; t_mc_string = (const char *)MCStringGetNativeCharPtr(p_string); t_mc_len = MCStringGetLength(p_string); } else { t_fd = s_iconv_unicode_to_sys_fd; t_mc_string = (const char *)MCStringGetCharPtr(p_string); t_mc_len = MCStringGetLength(p_string) * sizeof(unichar_t); } // Was creation of the iconv FD successful? if (!is_valid_iconv_fd(t_fd)) return false; // Perform the conversion bool t_success; char *t_sys_string; size_t t_sys_len; t_success = do_iconv(t_fd, t_mc_string, t_mc_len, t_sys_string, t_sys_len); if (!t_success) return false; // iconv doesn't append a null character char *t_term = (char*)realloc(t_sys_string, t_sys_len + 1); if (t_term == nil) { free(t_sys_string); return false; } t_sys_string = t_term; t_sys_string[t_sys_len] = '\0'; r_system_string = t_sys_string; r_byte_count = t_sys_len; return true; } #elif defined(__WINDOWS__) bool MCStringConvertToSysString(MCStringRef p_string, char *& r_system_string, size_t& r_byte_count) { __MCAssertIsString(p_string); UINT t_codepage; t_codepage = GetConsoleOutputCP(); int t_needed; t_needed = WideCharToMultiByte(t_codepage, WC_COMPOSITECHECK | WC_NO_BEST_FIT_CHARS, MCStringGetCharPtr(p_string), -1, NULL, 0, NULL, NULL); MCAutoArray t_bytes; if (!t_bytes . New(t_needed)) return false; if (WideCharToMultiByte(t_codepage, WC_COMPOSITECHECK | WC_NO_BEST_FIT_CHARS, MCStringGetCharPtr(p_string), -1, t_bytes . Ptr(), t_needed, NULL, NULL) == 0) return false; uindex_t t_size; char *t_ptr; t_bytes . Take(t_ptr, t_size); r_system_string = t_ptr; // Account for the fact that array size includes the NUL byte. r_byte_count = t_size - 1; return true; } #else bool MCStringConvertToSysString(MCStringRef p_string, char *& r_system_string, size_t& r_byte_count) { __MCAssertIsString(p_string); uindex_t t_byte_count; if (!MCStringConvertToUTF8(p_string, r_system_string, t_byte_count)) return false; r_byte_count = t_byte_count; return true; } bool MCStringCreateWithSysString(const char *p_sys_string, MCStringRef & r_string) { /* Fast path for empty string */ if (nil == p_sys_string || 0 == *p_sys_string) { r_string = MCValueRetain(kMCEmptyString); return true; } /* Count the number of chars */ size_t t_byte_count; for (t_byte_count = 0; p_sys_string[t_byte_count] != '\0'; ++t_byte_count); return MCStringCreateWithBytes((const byte_t *) p_sys_string, t_byte_count, kMCStringEncodingUTF8, false, /* is_external_rep */ r_string); } #endif bool MCStringCreateWithPascalString(const unsigned char* p_pascal_string, MCStringRef& r_string) { // The first byte of the string gives the length return MCStringCreateWithBytes(p_pascal_string+1, *p_pascal_string, kMCStringEncodingMacRoman, false, r_string); } static bool __MCStringCreateWithStrings(MCStringRef& r_string, bool p_has_separator, unichar_t p_separator, MCStringRef p_one, MCStringRef p_two) { bool t_success; t_success = true; // Create a new StringRef object __MCString *self; self = nil; if (t_success) t_success = __MCValueCreate(kMCValueTypeCodeString, self); // Resolve indirection if (__MCStringIsIndirect(p_one)) p_one = p_one->string; if (__MCStringIsIndirect(p_two)) p_two = p_two->string; // Calculate the required length and is-native status of the result string uindex_t t_one_length, t_two_length; t_one_length = __MCStringGetLength(p_one); t_two_length = __MCStringGetLength(p_two); bool t_native, t_can_be_native; t_native = __MCStringIsNative(p_one) && __MCStringIsNative(p_two); t_can_be_native = __MCStringCanBeNative(p_one) && __MCStringCanBeNative(p_two); uindex_t t_length = t_one_length + t_two_length; // Take the separator into account char_t t_native_separator; if (p_has_separator) { bool t_separator_native; t_separator_native = MCUnicodeCharMapToNative(p_separator, t_native_separator); t_native = t_native && t_separator_native; t_can_be_native = t_can_be_native && t_separator_native; t_length++; } // Increase the length so we always have a NUL terminator t_length++; // Are we doing a native or a Unicode concatenation? if (t_native) { // Allocate the output buffer if (t_success) t_success = MCMemoryAllocate(t_length, self->native_chars); if (t_success) { // Both sides are already native so the copy is simple MCMemoryCopy(self->native_chars, p_one->native_chars, t_one_length); if (p_has_separator) { // Add the separator character self->native_chars[t_one_length] = t_native_separator; t_one_length++; } MCMemoryCopy(self->native_chars + t_one_length, p_two->native_chars, t_two_length); // Terminate the string self->char_count = t_length-1; self->native_chars[self->char_count] = '\0'; } } else { // Allocate the output buffer if (t_success) t_success = MCMemoryAllocate(t_length * sizeof(unichar_t), self->chars); if (t_success) { // Copy the first string if (!__MCStringIsNative(p_one)) MCMemoryCopy(self->chars, p_one->chars, t_one_length*sizeof(unichar_t)); else { // Copy and map each character const char_t* t_one_ptr = p_one->native_chars; for (uindex_t i = 0; i < t_one_length; i++) self->chars[i] = MCUnicodeCharMapFromNative(t_one_ptr[i]); } if (p_has_separator) { // Add the separator character self->chars[t_one_length] = p_separator; t_one_length++; } // Copy the second string if (!__MCStringIsNative(p_two)) MCMemoryCopy(self->chars + t_one_length, p_two->chars, t_two_length*sizeof(unichar_t)); else { // Copy and map each character const char_t* t_two_ptr = p_two->native_chars; for (uindex_t i = 0; i < t_two_length; i++) self->chars[t_one_length + i] = MCUnicodeCharMapFromNative(t_two_ptr[i]); } // Terminate the string self->char_count = t_length-1; self->chars[self->char_count] = '\0'; // Set the flags self->flags |= kMCStringFlagIsNotNative; } } // If both sides and the separator can be native, set the flag if (t_can_be_native) self->flags |= kMCStringFlagCanBeNative; // Set the output value if (t_success) r_string = self; return t_success; } //////////////////////////////////////////////////////////////////////// #if defined(__WINDOWS__) MC_DLLEXPORT_DEF bool MCStringCreateWithBSTR(const BSTR p_bstr, MCStringRef& r_string) { return MCStringCreateWithChars(p_bstr, SysStringLen(p_bstr), r_string); } #endif /*__WINDOWS__*/ //////////////////////////////////////////////////////////////////////// MC_DLLEXPORT_DEF bool MCStringCreateWithStrings(MCStringRef& r_string, MCStringRef p_one, MCStringRef p_two) { __MCAssertIsString(p_one); __MCAssertIsString(p_two); return __MCStringCreateWithStrings(r_string, false, 0, p_one, p_two); } MC_DLLEXPORT_DEF bool MCStringCreateWithStringsAndSeparator(MCStringRef& r_string, unichar_t p_separator, MCStringRef p_one, MCStringRef p_two) { __MCAssertIsString(p_one); __MCAssertIsString(p_two); return __MCStringCreateWithStrings(r_string, true, p_separator, p_one, p_two); } MC_DLLEXPORT_DEF bool MCStringNormalizedCopyNFC(MCStringRef self, MCStringRef &r_string) { __MCAssertIsString(self); if (MCStringIsNative(self)) return MCStringCopy(self, r_string); // Normalise unichar_t *t_norm = nil; uindex_t t_norm_length; if (MCUnicodeNormaliseNFC(self -> chars, self -> char_count, t_norm, t_norm_length) && MCStringCreateWithCharsAndRelease(t_norm, t_norm_length, r_string)) return true; MCMemoryDelete(t_norm); return false; } MC_DLLEXPORT_DEF bool MCStringNormalizedCopyNFD(MCStringRef self, MCStringRef &r_string) { __MCAssertIsString(self); // AL-2014-06-24: [[ Bug 12656 ]] Native strings can be decomposed into non-native ones. unichar_t *t_norm = nil; uindex_t t_norm_length; if (MCUnicodeNormaliseNFD(MCStringGetCharPtr(self), self -> char_count, t_norm, t_norm_length) && MCStringCreateWithCharsAndRelease(t_norm, t_norm_length, r_string)) return true; MCMemoryDelete(t_norm); return false; } MC_DLLEXPORT_DEF bool MCStringNormalizedCopyNFKC(MCStringRef self, MCStringRef &r_string) { __MCAssertIsString(self); // Native strings are already normalized if (MCStringIsNative(self)) return MCStringCopy(self, r_string); // Normalise unichar_t *t_norm = nil; uindex_t t_norm_length; if (MCUnicodeNormaliseNFKC(self -> chars, self -> char_count, t_norm, t_norm_length) && MCStringCreateWithCharsAndRelease(t_norm, t_norm_length, r_string)) return true; MCMemoryDelete(t_norm); return false; } MC_DLLEXPORT_DEF bool MCStringNormalizedCopyNFKD(MCStringRef self, MCStringRef &r_string) { __MCAssertIsString(self); // AL-2014-06-24: [[ Bug 12656 ]] Native strings can be decomposed into non-native ones. // Normalise unichar_t *t_norm = nil; uindex_t t_norm_length; if (MCUnicodeNormaliseNFKD(MCStringGetCharPtr(self), self -> char_count, t_norm, t_norm_length) && MCStringCreateWithCharsAndRelease(t_norm, t_norm_length, r_string)) return true; MCMemoryDelete(t_norm); return false; } ///////// MC_DLLEXPORT_DEF bool MCStringSetNumericValue(MCStringRef self, double p_value) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; if (MCStringIsMutable(self)) return false; self -> numeric_value = p_value; self -> flags |= kMCStringFlagHasNumber; return true; } MC_DLLEXPORT_DEF bool MCStringGetNumericValue(MCStringRef self, double &r_value) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; if ((self -> flags & kMCStringFlagHasNumber) != 0) { r_value = self -> numeric_value; return true; } else return false; } MC_DLLEXPORT bool MCStringNormalizeLineEndings(MCStringRef p_input, MCStringLineEndingStyle p_to_style, MCStringLineEndingOptions p_options, MCStringRef& r_output, MCStringLineEndingStyle* r_original_style) { MCStringLineEndingStyle t_original_style = kMCStringLineEndingStyleLF; uindex_t t_first_lf = 0; uindex_t t_first_cr = 0; if (MCStringFirstIndexOfChar(p_input, '\r', 0, kMCStringOptionCompareExact, t_first_cr)) { if (MCStringFirstIndexOfChar(p_input, '\n', 0, kMCStringOptionCompareExact, t_first_lf)) { if (t_first_cr + 1 == t_first_lf) { t_original_style = kMCStringLineEndingStyleCRLF; } else if (t_first_cr < t_first_lf) { t_original_style = kMCStringLineEndingStyleCR; } } else { t_original_style = kMCStringLineEndingStyleCR; } } // normalize input to LF line endings first MCAutoStringRef t_mutable_input; if (!MCStringMutableCopy(p_input, &t_mutable_input)) { return false; } if (!MCStringFindAndReplace(*t_mutable_input, MCSTR("\r\n"), MCSTR("\n"), kMCStringOptionCompareExact)) { return false; } if (!MCStringFindAndReplace(*t_mutable_input, MCSTR("\r"), MCSTR("\n"), kMCStringOptionCompareExact)) { return false; } // AL-2014-07-21: [[ Bug 12162 ]] Convert PS to LF, and LS to VT on text import. if (((p_options & kMCStringLineEndingOptionNormalizeLSToVT) != 0) && !MCStringFindAndReplaceChar(*t_mutable_input, (const codepoint_t)0x2028, (const codepoint_t)0x0B, kMCStringOptionCompareExact)) { return false; } if (((p_options & kMCStringLineEndingOptionNormalizePSToLineEnding) != 0) && !MCStringFindAndReplaceChar(*t_mutable_input, (const codepoint_t)0x2029, (const codepoint_t)0x0A, kMCStringOptionCompareExact)) { return false; } // now convert the line endings to the proper version if ((p_to_style == kMCStringLineEndingStyleCR) && !MCStringFindAndReplace(*t_mutable_input, MCSTR("\n"), MCSTR("\r"), kMCStringOptionCompareExact)) { return false; } else if ((p_to_style == kMCStringLineEndingStyleCRLF) && !MCStringFindAndReplace(*t_mutable_input, MCSTR("\n"), MCSTR("\r\n"), kMCStringOptionCompareExact)) { return false; } if (!t_mutable_input.MakeImmutable()) { return false; } r_output = t_mutable_input.Take(); if (r_original_style != nullptr) { *r_original_style = t_original_style; } return true; } static void __MCStringCheck(MCStringRef self) { __MCAssertIsString(self); if (__MCStringIsIndirect(self)) self = self -> string; if (__MCStringIsChecked(self)) return; if (__MCStringCanBeNative(self)) return; bool t_can_be_native; t_can_be_native = true; for (uindex_t i = 0; i < self -> char_count; i++) { if (MCStringIsValidSurrogatePair(self, i)) { __MCStringSetFlags(self, false, false, false); t_can_be_native = false; break; } if (!MCUnicodeIsGraphemeClusterBoundary(self -> chars[i], self -> chars[i + 1])) { __MCStringSetFlags(self, kMCStringFlagNoChange, false, false); t_can_be_native = false; break; } char_t t_native; if (!MCUnicodeCharMapToNative(self -> chars[i], t_native)) { t_can_be_native = false; break; } } if (t_can_be_native) { __MCStringSetFlags(self, true, true, true); } self -> flags |= kMCStringFlagIsChecked; } //////////////////////////////////////////////////////////////////////////////// static bool __MCStringCreateIndirect(__MCString *string, __MCString*& r_string) { MCStringRef self; if (!__MCValueCreate(kMCValueTypeCodeString, self)) return false; self -> string = MCValueRetain(string); self -> flags |= kMCStringFlagIsIndirect | kMCStringFlagIsMutable; r_string = self; return true; } // Returns true if the string is indirect. static bool __MCStringIsIndirect(__MCString *self) { return (self -> flags & kMCStringFlagIsIndirect) != 0; } static bool __MCStringMakeImmutable(__MCString *self) { // Shrink the char buffer to be just long enough for the characters plus // an implicit NUL. if (MCStringIsNative(self)) { if (!MCMemoryResizeArray(self -> char_count + 1, self -> native_chars, self -> char_count)) return false; } else { if (!MCMemoryResizeArray(self -> char_count + 1, self -> chars, self -> char_count)) return false; } self -> char_count -= 1; return true; } // Creates an immutable string from this one, changing 'self' to indirect. static bool __MCStringMakeIndirect(__MCString *self) { // If we are already indirect, there's nothing to do. if (__MCStringIsIndirect(self)) return true; if (!__MCStringMakeImmutable(self)) return false; // Create a new direct string for self to reference MCStringRef t_string; if (!__MCValueCreate(kMCValueTypeCodeString, t_string)) return false; // Share the buffer and assign flags & count t_string -> flags |= self -> flags; t_string -> flags &= ~kMCStringFlagIsMutable; t_string -> char_count = self -> char_count; if (__MCStringIsNative(self)) t_string -> native_chars = self -> native_chars; else { t_string -> chars = self -> chars; t_string -> flags |= kMCStringFlagIsNotNative; // AL-2015-02-05: [[ Bug 14504 ]] Ensure 'CanBeNative' flag is preserved when making a string indirect if (__MCStringCanBeNative(self)) t_string -> flags |= kMCStringFlagCanBeNative; } // 'self' now becomes indirect with a reference to the new string. self -> flags |= kMCStringFlagIsIndirect; self -> string = t_string; return true; } // Ensures the given mutable but indirect string is direct. static bool __MCStringResolveIndirect(__MCString *self) { // Make sure we are indirect. MCAssert(__MCStringIsIndirect(self)); // Fetch the reference. MCStringRef t_string; t_string = self -> string; // If the string only has a single reference, then re-absorb; otherwise // copy. if (self -> string -> references == 1) { self -> char_count = t_string -> char_count; self -> capacity = t_string -> capacity; self -> flags |= t_string -> flags; if (__MCStringIsNative(t_string)) self -> native_chars = t_string -> native_chars; else { self -> chars = t_string -> chars; self -> flags |= kMCStringFlagIsNotNative; // AL-2015-02-05: [[ Bug 14504 ]] Ensure 'CanBeNative' flag is preserved when making resolving an indirect string. if (__MCStringCanBeNative(t_string)) self -> flags |= kMCStringFlagCanBeNative; } t_string -> char_count = 0; t_string -> chars = nil; t_string -> native_chars = nil; MCValueRelease(t_string); } else { // SN-2015-01-13: [[ Bug 14354 ]] We don't want to release the string we reference // before being sure that we can clone its buffer. unichar_t *t_chars; uint32_t t_char_count; if (__MCStringIsNative(t_string)) { char_t *t_native_chars; if (!__MCStringCloneNativeBuffer(t_string, t_native_chars, t_char_count)) return false; t_chars = (unichar_t*)t_native_chars; } else { if (!__MCStringCloneBuffer(t_string, t_chars, t_char_count)) return false; self -> flags |= kMCStringFlagIsNotNative; // AL-2015-02-05: [[ Bug 14504 ]] Ensure 'CanBeNative' flag is preserved when making resolving an indirect string. if (__MCStringCanBeNative(t_string)) self -> flags |= kMCStringFlagCanBeNative; } // SN-2015-01-13: [[ Bug 14354 ]] We can release now release the string, // and then change the value of the string attributes MCValueRelease(self -> string); self -> chars = t_chars; self -> char_count = t_char_count; self -> capacity = t_string -> char_count; } self -> flags &= ~kMCStringFlagIsIndirect; return true; } static bool __MCStringCopyMutable(__MCString *self, __MCString*& r_new_string) { if (!__MCStringMakeImmutable(self)) return false; __MCString *t_string; t_string = nil; if (self -> char_count == 0) { t_string = MCValueRetain(kMCEmptyString); MCMemoryDeleteArray(self -> native_chars); } else { if (!__MCValueCreate(kMCValueTypeCodeString, t_string)) return false; t_string -> char_count = self -> char_count; if (__MCStringIsNative(self)) t_string -> native_chars = self -> native_chars; else { t_string -> chars = self -> chars; t_string -> flags |= kMCStringFlagIsNotNative; // AL-2015-02-05: [[ Bug 14504 ]] Ensure 'CanBeNative' flag is preserved when making a new direct string. if (__MCStringCanBeNative(self)) t_string -> flags |= kMCStringFlagCanBeNative; } t_string -> capacity = 0; } self -> char_count = 0; self -> chars = nil; self -> native_chars = nil; self -> string = MCValueRetain(t_string); self -> flags |= kMCStringFlagIsIndirect; r_new_string = t_string; return true; }

Back | FazBrowse Home | New Git URL