/* * Copyright (c) 2008-2021 Jonathan Schleifer * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE.QPL included in * the packaging of this file. * * Alternatively, it may be distributed under the terms of the GNU General * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #include "config.h" #include #include #include #include #include #include #if defined(HAVE_STRTOF_L) || defined(HAVE_STRTOD_L) # include #endif #ifdef HAVE_XLOCALE_H # include #endif #import "OFString.h" #import "OFArray.h" #import "OFCharacterSet.h" #import "OFData.h" #import "OFDictionary.h" #ifdef OF_HAVE_FILES # import "OFFile.h" # import "OFFileManager.h" #endif #import "OFLocale.h" #import "OFStream.h" #import "OFSystemInfo.h" #import "OFURL.h" #import "OFURLHandler.h" #import "OFUTF8String.h" #import "OFUTF8String+Private.h" #import "OFXMLElement.h" #import "OFInitializationFailedException.h" #import "OFInvalidArgumentException.h" #import "OFInvalidEncodingException.h" #import "OFInvalidFormatException.h" #import "OFNotImplementedException.h" #import "OFOpenItemFailedException.h" #import "OFOutOfMemoryException.h" #import "OFOutOfRangeException.h" #import "OFRetrieveItemAttributesFailedException.h" #import "OFTruncatedDataException.h" #import "OFUnsupportedProtocolException.h" #import "of_asprintf.h" #import "unicode.h" /* * It seems strtod is buggy on Win32. * However, the MinGW version __strtod seems to be ok. */ #ifdef __MINGW32__ # define strtod __strtod #endif #ifndef HAVE_STRTOF # define strtof strtod #endif #ifndef INFINITY # define INFINITY __builtin_inf() #endif static struct { Class isa; } placeholder; #if defined(HAVE_STRTOF_L) || defined(HAVE_STRTOD_L) static locale_t cLocale; #endif @interface OFString () - (size_t)of_getCString: (char *)cString maxLength: (size_t)maxLength encoding: (of_string_encoding_t)encoding lossy: (bool)lossy OF_DIRECT; - (const char *)of_cStringWithEncoding: (of_string_encoding_t)encoding lossy: (bool)lossy OF_DIRECT; - (OFString *)of_JSONRepresentationWithOptions: (int)options depth: (size_t)depth; @end @interface OFStringPlaceholder: OFString @end extern bool of_unicode_to_iso_8859_2(const of_unichar_t *, unsigned char *, size_t, bool); extern bool of_unicode_to_iso_8859_3(const of_unichar_t *, unsigned char *, size_t, bool); extern bool of_unicode_to_iso_8859_15(const of_unichar_t *, unsigned char *, size_t, bool); extern bool of_unicode_to_windows_1251(const of_unichar_t *, unsigned char *, size_t, bool); extern bool of_unicode_to_windows_1252(const of_unichar_t *, unsigned char *, size_t, bool); extern bool of_unicode_to_codepage_437(const of_unichar_t *, unsigned char *, size_t, bool); extern bool of_unicode_to_codepage_850(const of_unichar_t *, unsigned char *, size_t, bool); extern bool of_unicode_to_codepage_858(const of_unichar_t *, unsigned char *, size_t, bool); extern bool of_unicode_to_mac_roman(const of_unichar_t *, unsigned char *, size_t, bool); extern bool of_unicode_to_koi8_r(const of_unichar_t *, unsigned char *, size_t, bool); extern bool of_unicode_to_koi8_u(const of_unichar_t *, unsigned char *, size_t, bool); /* References for static linking */ void _references_to_categories_of_OFString(void) { _OFString_CryptoHashing_reference = 1; _OFString_JSONParsing_reference = 1; #ifdef OF_HAVE_FILES _OFString_PathAdditions_reference = 1; #endif _OFString_PropertyListParsing_reference = 1; _OFString_Serialization_reference = 1; _OFString_URLEncoding_reference = 1; _OFString_XMLEscaping_reference = 1; _OFString_XMLUnescaping_reference = 1; } void _reference_to_OFConstantString(void) { [OFConstantString class]; } of_string_encoding_t of_string_parse_encoding(OFString *string) { void *pool = objc_autoreleasePoolPush(); of_string_encoding_t encoding; string = string.lowercaseString; if ([string isEqual: @"utf8"] || [string isEqual: @"utf-8"]) encoding = OF_STRING_ENCODING_UTF_8; else if ([string isEqual: @"ascii"] || [string isEqual: @"us-ascii"]) encoding = OF_STRING_ENCODING_ASCII; else if ([string isEqual: @"iso-8859-1"] || [string isEqual: @"iso_8859-1"]) encoding = OF_STRING_ENCODING_ISO_8859_1; else if ([string isEqual: @"iso-8859-2"] || [string isEqual: @"iso_8859-2"]) encoding = OF_STRING_ENCODING_ISO_8859_2; else if ([string isEqual: @"iso-8859-3"] || [string isEqual: @"iso_8859-3"]) encoding = OF_STRING_ENCODING_ISO_8859_3; else if ([string isEqual: @"iso-8859-15"] || [string isEqual: @"iso_8859-15"]) encoding = OF_STRING_ENCODING_ISO_8859_15; else if ([string isEqual: @"windows-1251"] || [string isEqual: @"cp1251"] || [string isEqual: @"cp-1251"] || [string isEqual: @"1251"]) encoding = OF_STRING_ENCODING_WINDOWS_1251; else if ([string isEqual: @"windows-1252"] || [string isEqual: @"cp1252"] || [string isEqual: @"cp-1252"] || [string isEqual: @"1252"]) encoding = OF_STRING_ENCODING_WINDOWS_1252; else if ([string isEqual: @"cp437"] || [string isEqual: @"cp-437"] || [string isEqual: @"ibm437"] || [string isEqual: @"437"]) encoding = OF_STRING_ENCODING_CODEPAGE_437; else if ([string isEqual: @"cp850"] || [string isEqual: @"cp-850"] || [string isEqual: @"ibm850"] || [string isEqual: @"850"]) encoding = OF_STRING_ENCODING_CODEPAGE_850; else if ([string isEqual: @"cp858"] || [string isEqual: @"cp-858"] || [string isEqual: @"ibm858"] || [string isEqual: @"858"]) encoding = OF_STRING_ENCODING_CODEPAGE_858; else if ([string isEqual: @"macintosh"] || [string isEqual: @"mac"]) encoding = OF_STRING_ENCODING_MAC_ROMAN; else if ([string isEqual: @"koi8-r"]) encoding = OF_STRING_ENCODING_KOI8_R; else if ([string isEqual: @"koi8-u"]) encoding = OF_STRING_ENCODING_KOI8_U; else @throw [OFInvalidArgumentException exception]; objc_autoreleasePoolPop(pool); return encoding; } OFString * of_string_name_of_encoding(of_string_encoding_t encoding) { switch (encoding) { case OF_STRING_ENCODING_UTF_8: return @"UTF-8"; case OF_STRING_ENCODING_ASCII: return @"ASCII"; case OF_STRING_ENCODING_ISO_8859_1: return @"ISO 8859-1"; case OF_STRING_ENCODING_ISO_8859_2: return @"ISO 8859-2"; case OF_STRING_ENCODING_ISO_8859_3: return @"ISO 8859-3"; case OF_STRING_ENCODING_ISO_8859_15: return @"ISO 8859-15"; case OF_STRING_ENCODING_WINDOWS_1251: return @"Windows-1251"; case OF_STRING_ENCODING_WINDOWS_1252: return @"Windows-1252"; case OF_STRING_ENCODING_CODEPAGE_437: return @"Codepage 437"; case OF_STRING_ENCODING_CODEPAGE_850: return @"Codepage 850"; case OF_STRING_ENCODING_CODEPAGE_858: return @"Codepage 858"; case OF_STRING_ENCODING_MAC_ROMAN: return @"Mac Roman"; case OF_STRING_ENCODING_KOI8_R: return @"KOI8-R"; case OF_STRING_ENCODING_KOI8_U: return @"KOI8-U"; case OF_STRING_ENCODING_AUTODETECT: return @"autodetect"; } return nil; } size_t of_string_utf8_encode(of_unichar_t character, char *buffer) { if (character < 0x80) { buffer[0] = character; return 1; } else if (character < 0x800) { buffer[0] = 0xC0 | (character >> 6); buffer[1] = 0x80 | (character & 0x3F); return 2; } else if (character < 0x10000) { buffer[0] = 0xE0 | (character >> 12); buffer[1] = 0x80 | (character >> 6 & 0x3F); buffer[2] = 0x80 | (character & 0x3F); return 3; } else if (character < 0x110000) { buffer[0] = 0xF0 | (character >> 18); buffer[1] = 0x80 | (character >> 12 & 0x3F); buffer[2] = 0x80 | (character >> 6 & 0x3F); buffer[3] = 0x80 | (character & 0x3F); return 4; } return 0; } ssize_t of_string_utf8_decode(const char *buffer_, size_t length, of_unichar_t *ret) { const unsigned char *buffer = (const unsigned char *)buffer_; if (!(*buffer & 0x80)) { *ret = buffer[0]; return 1; } if ((*buffer & 0xE0) == 0xC0) { if OF_UNLIKELY (length < 2) return -2; if OF_UNLIKELY ((buffer[1] & 0xC0) != 0x80) return 0; *ret = ((buffer[0] & 0x1F) << 6) | (buffer[1] & 0x3F); return 2; } if ((*buffer & 0xF0) == 0xE0) { if OF_UNLIKELY (length < 3) return -3; if OF_UNLIKELY ((buffer[1] & 0xC0) != 0x80 || (buffer[2] & 0xC0) != 0x80) return 0; *ret = ((buffer[0] & 0x0F) << 12) | ((buffer[1] & 0x3F) << 6) | (buffer[2] & 0x3F); return 3; } if ((*buffer & 0xF8) == 0xF0) { if OF_UNLIKELY (length < 4) return -4; if OF_UNLIKELY ((buffer[1] & 0xC0) != 0x80 || (buffer[2] & 0xC0) != 0x80 || (buffer[3] & 0xC0) != 0x80) return 0; *ret = ((buffer[0] & 0x07) << 18) | ((buffer[1] & 0x3F) << 12) | ((buffer[2] & 0x3F) << 6) | (buffer[3] & 0x3F); return 4; } return 0; } size_t of_string_utf16_length(const of_char16_t *string) { size_t length = 0; while (*string++ != 0) length++; return length; } size_t of_string_utf32_length(const of_char32_t *string) { size_t length = 0; while (*string++ != 0) length++; return length; } #ifdef OF_HAVE_UNICODE_TABLES static OFString * decomposedString(OFString *self, const char *const *const *table, size_t size) { OFMutableString *ret = [OFMutableString string]; void *pool = objc_autoreleasePoolPush(); const of_unichar_t *characters = self.characters; size_t length = self.length; for (size_t i = 0; i < length; i++) { of_unichar_t c = characters[i]; const char *const *page; if (c >= size) { [ret appendCharacters: &c length: 1]; continue; } page = table[c >> 8]; if (page != NULL && page[c & 0xFF] != NULL) [ret appendUTF8String: page[c & 0xFF]]; else [ret appendCharacters: &c length: 1]; } objc_autoreleasePoolPop(pool); return ret; } #endif @implementation OFStringPlaceholder - (instancetype)init { return (id)[[OFUTF8String alloc] init]; } - (instancetype)initWithUTF8String: (const char *)UTF8String { OFUTF8String *string; size_t length; void *storage; length = strlen(UTF8String); string = of_alloc_object([OFUTF8String class], length + 1, 1, &storage); return (id)[string of_initWithUTF8String: UTF8String length: length storage: storage]; } - (instancetype)initWithUTF8String: (const char *)UTF8String length: (size_t)UTF8StringLength { OFUTF8String *string; void *storage; string = of_alloc_object([OFUTF8String class], UTF8StringLength + 1, 1, &storage); return (id)[string of_initWithUTF8String: UTF8String length: UTF8StringLength storage: storage]; } - (instancetype)initWithUTF8StringNoCopy: (char *)UTF8String freeWhenDone: (bool)freeWhenDone { return (id)[[OFUTF8String alloc] initWithUTF8StringNoCopy: UTF8String freeWhenDone: freeWhenDone]; } - (instancetype)initWithUTF8StringNoCopy: (char *)UTF8String length: (size_t)UTF8StringLength freeWhenDone: (bool)freeWhenDone { return (id)[[OFUTF8String alloc] initWithUTF8StringNoCopy: UTF8String length: UTF8StringLength freeWhenDone: freeWhenDone]; } - (instancetype)initWithCString: (const char *)cString encoding: (of_string_encoding_t)encoding { if (encoding == OF_STRING_ENCODING_UTF_8) { OFUTF8String *string; size_t length; void *storage; length = strlen(cString); string = of_alloc_object([OFUTF8String class], length + 1, 1, &storage); return (id)[string of_initWithUTF8String: cString length: length storage: storage]; } return (id)[[OFUTF8String alloc] initWithCString: cString encoding: encoding]; } - (instancetype)initWithCString: (const char *)cString encoding: (of_string_encoding_t)encoding length: (size_t)cStringLength { if (encoding == OF_STRING_ENCODING_UTF_8) { OFUTF8String *string; void *storage; string = of_alloc_object([OFUTF8String class], cStringLength + 1, 1, &storage); return (id)[string of_initWithUTF8String: cString length: cStringLength storage: storage]; } return (id)[[OFUTF8String alloc] initWithCString: cString encoding: encoding length: cStringLength]; } - (instancetype)initWithData: (OFData *)data encoding: (of_string_encoding_t)encoding { return (id)[[OFUTF8String alloc] initWithData: data encoding: encoding]; } - (instancetype)initWithString: (OFString *)string { return (id)[[OFUTF8String alloc] initWithString: string]; } - (instancetype)initWithCharacters: (const of_unichar_t *)string length: (size_t)length { return (id)[[OFUTF8String alloc] initWithCharacters: string length: length]; } - (instancetype)initWithUTF16String: (const of_char16_t *)string { return (id)[[OFUTF8String alloc] initWithUTF16String: string]; } - (instancetype)initWithUTF16String: (const of_char16_t *)string length: (size_t)length { return (id)[[OFUTF8String alloc] initWithUTF16String: string length: length]; } - (instancetype)initWithUTF16String: (const of_char16_t *)string byteOrder: (of_byte_order_t)byteOrder { return (id)[[OFUTF8String alloc] initWithUTF16String: string byteOrder: byteOrder]; } - (instancetype)initWithUTF16String: (const of_char16_t *)string length: (size_t)length byteOrder: (of_byte_order_t)byteOrder { return (id)[[OFUTF8String alloc] initWithUTF16String: string length: length byteOrder: byteOrder]; } - (instancetype)initWithUTF32String: (const of_char32_t *)string { return (id)[[OFUTF8String alloc] initWithUTF32String: string]; } - (instancetype)initWithUTF32String: (const of_char32_t *)string length: (size_t)length { return (id)[[OFUTF8String alloc] initWithUTF32String: string length: length]; } - (instancetype)initWithUTF32String: (const of_char32_t *)string byteOrder: (of_byte_order_t)byteOrder { return (id)[[OFUTF8String alloc] initWithUTF32String: string byteOrder: byteOrder]; } - (instancetype)initWithUTF32String: (const of_char32_t *)string length: (size_t)length byteOrder: (of_byte_order_t)byteOrder { return (id)[[OFUTF8String alloc] initWithUTF32String: string length: length byteOrder: byteOrder]; } - (instancetype)initWithFormat: (OFConstantString *)format, ... { id ret; va_list arguments; va_start(arguments, format); ret = [[OFUTF8String alloc] initWithFormat: format arguments: arguments]; va_end(arguments); return ret; } - (instancetype)initWithFormat: (OFConstantString *)format arguments: (va_list)arguments { return (id)[[OFUTF8String alloc] initWithFormat: format arguments: arguments]; } #ifdef OF_HAVE_FILES - (instancetype)initWithContentsOfFile: (OFString *)path { return (id)[[OFUTF8String alloc] initWithContentsOfFile: path]; } - (instancetype)initWithContentsOfFile: (OFString *)path encoding: (of_string_encoding_t)encoding { return (id)[[OFUTF8String alloc] initWithContentsOfFile: path encoding: encoding]; } #endif #if defined(OF_HAVE_FILES) || defined(OF_HAVE_SOCKETS) - (instancetype)initWithContentsOfURL: (OFURL *)URL { return (id)[[OFUTF8String alloc] initWithContentsOfURL: URL]; } - (instancetype)initWithContentsOfURL: (OFURL *)URL encoding: (of_string_encoding_t)encoding { return (id)[[OFUTF8String alloc] initWithContentsOfURL: URL encoding: encoding]; } #endif - (instancetype)initWithSerialization: (OFXMLElement *)element { return (id)[[OFUTF8String alloc] initWithSerialization: element]; } - (instancetype)retain { return self; } - (instancetype)autorelease { return self; } - (void)release { } - (void)dealloc { OF_DEALLOC_UNSUPPORTED } @end @implementation OFString + (void)initialize { if (self != [OFString class]) return; placeholder.isa = [OFStringPlaceholder class]; #if defined(HAVE_STRTOF_L) || defined(HAVE_STRTOD_L) if ((cLocale = newlocale(LC_ALL_MASK, "C", NULL)) == NULL) @throw [OFInitializationFailedException exceptionWithClass: self]; #endif } + (instancetype)alloc { if (self == [OFString class]) return (id)&placeholder; return [super alloc]; } + (instancetype)string { return [[[self alloc] init] autorelease]; } + (instancetype)stringWithUTF8String: (const char *)UTF8String { return [[[self alloc] initWithUTF8String: UTF8String] autorelease]; } + (instancetype)stringWithUTF8String: (const char *)UTF8String length: (size_t)UTF8StringLength { return [[[self alloc] initWithUTF8String: UTF8String length: UTF8StringLength] autorelease]; } + (instancetype)stringWithUTF8StringNoCopy: (char *)UTF8String freeWhenDone: (bool)freeWhenDone { return [[[self alloc] initWithUTF8StringNoCopy: UTF8String freeWhenDone: freeWhenDone] autorelease]; } + (instancetype)stringWithUTF8StringNoCopy: (char *)UTF8String length: (size_t)UTF8StringLength freeWhenDone: (bool)freeWhenDone { return [[[self alloc] initWithUTF8StringNoCopy: UTF8String length: UTF8StringLength freeWhenDone: freeWhenDone] autorelease]; } + (instancetype)stringWithCString: (const char *)cString encoding: (of_string_encoding_t)encoding { return [[[self alloc] initWithCString: cString encoding: encoding] autorelease]; } + (instancetype)stringWithCString: (const char *)cString encoding: (of_string_encoding_t)encoding length: (size_t)cStringLength { return [[[self alloc] initWithCString: cString encoding: encoding length: cStringLength] autorelease]; } + (instancetype)stringWithData: (OFData *)data encoding: (of_string_encoding_t)encoding { return [[[self alloc] initWithData: data encoding: encoding] autorelease]; } + (instancetype)stringWithString: (OFString *)string { return [[[self alloc] initWithString: string] autorelease]; } + (instancetype)stringWithCharacters: (const of_unichar_t *)string length: (size_t)length { return [[[self alloc] initWithCharacters: string length: length] autorelease]; } + (instancetype)stringWithUTF16String: (const of_char16_t *)string { return [[[self alloc] initWithUTF16String: string] autorelease]; } + (instancetype)stringWithUTF16String: (const of_char16_t *)string length: (size_t)length { return [[[self alloc] initWithUTF16String: string length: length] autorelease]; } + (instancetype)stringWithUTF16String: (const of_char16_t *)string byteOrder: (of_byte_order_t)byteOrder { return [[[self alloc] initWithUTF16String: string byteOrder: byteOrder] autorelease]; } + (instancetype)stringWithUTF16String: (const of_char16_t *)string length: (size_t)length byteOrder: (of_byte_order_t)byteOrder { return [[[self alloc] initWithUTF16String: string length: length byteOrder: byteOrder] autorelease]; } + (instancetype)stringWithUTF32String: (const of_char32_t *)string { return [[[self alloc] initWithUTF32String: string] autorelease]; } + (instancetype)stringWithUTF32String: (const of_char32_t *)string length: (size_t)length { return [[[self alloc] initWithUTF32String: string length: length] autorelease]; } + (instancetype)stringWithUTF32String: (const of_char32_t *)string byteOrder: (of_byte_order_t)byteOrder { return [[[self alloc] initWithUTF32String: string byteOrder: byteOrder] autorelease]; } + (instancetype)stringWithUTF32String: (const of_char32_t *)string length: (size_t)length byteOrder: (of_byte_order_t)byteOrder { return [[[self alloc] initWithUTF32String: string length: length byteOrder: byteOrder] autorelease]; } + (instancetype)stringWithFormat: (OFConstantString *)format, ... { id ret; va_list arguments; va_start(arguments, format); ret = [[[self alloc] initWithFormat: format arguments: arguments] autorelease]; va_end(arguments); return ret; } #ifdef OF_HAVE_FILES + (instancetype)stringWithContentsOfFile: (OFString *)path { return [[[self alloc] initWithContentsOfFile: path] autorelease]; } + (instancetype)stringWithContentsOfFile: (OFString *)path encoding: (of_string_encoding_t)encoding { return [[[self alloc] initWithContentsOfFile: path encoding: encoding] autorelease]; } #endif #if defined(OF_HAVE_FILES) || defined(OF_HAVE_SOCKETS) + (instancetype)stringWithContentsOfURL: (OFURL *)URL { return [[[self alloc] initWithContentsOfURL: URL] autorelease]; } + (instancetype)stringWithContentsOfURL: (OFURL *)URL encoding: (of_string_encoding_t)encoding { return [[[self alloc] initWithContentsOfURL: URL encoding: encoding] autorelease]; } #endif - (instancetype)init { if ([self isMemberOfClass: [OFString class]]) { @try { [self doesNotRecognizeSelector: _cmd]; } @catch (id e) { [self release]; @throw e; } abort(); } return [super init]; } - (instancetype)initWithUTF8String: (const char *)UTF8String { return [self initWithCString: UTF8String encoding: OF_STRING_ENCODING_UTF_8 length: strlen(UTF8String)]; } - (instancetype)initWithUTF8String: (const char *)UTF8String length: (size_t)UTF8StringLength { return [self initWithCString: UTF8String encoding: OF_STRING_ENCODING_UTF_8 length: UTF8StringLength]; } - (instancetype)initWithUTF8StringNoCopy: (char *)UTF8String freeWhenDone: (bool)freeWhenDone { id ret = [self initWithUTF8String: UTF8String]; if (freeWhenDone) free(UTF8String); return ret; } - (instancetype)initWithUTF8StringNoCopy: (char *)UTF8String length: (size_t)UTF8StringLength freeWhenDone: (bool)freeWhenDone { id ret = [self initWithUTF8String: UTF8String length: UTF8StringLength]; if (freeWhenDone) free(UTF8String); return ret; } - (instancetype)initWithCString: (const char *)cString encoding: (of_string_encoding_t)encoding { return [self initWithCString: cString encoding: encoding length: strlen(cString)]; } - (instancetype)initWithCString: (const char *)cString encoding: (of_string_encoding_t)encoding length: (size_t)cStringLength { OF_INVALID_INIT_METHOD } - (instancetype)initWithData: (OFData *)data encoding: (of_string_encoding_t)encoding { @try { if (data.itemSize != 1) @throw [OFInvalidArgumentException exception]; } @catch (id e) { [self release]; @throw e; } self = [self initWithCString: data.items encoding: encoding length: data.count]; return self; } - (instancetype)initWithString: (OFString *)string { OF_INVALID_INIT_METHOD } - (instancetype)initWithCharacters: (const of_unichar_t *)string length: (size_t)length { OF_INVALID_INIT_METHOD } - (instancetype)initWithUTF16String: (const of_char16_t *)string { return [self initWithUTF16String: string length: of_string_utf16_length(string) byteOrder: OF_BYTE_ORDER_NATIVE]; } - (instancetype)initWithUTF16String: (const of_char16_t *)string length: (size_t)length { return [self initWithUTF16String: string length: length byteOrder: OF_BYTE_ORDER_NATIVE]; } - (instancetype)initWithUTF16String: (const of_char16_t *)string byteOrder: (of_byte_order_t)byteOrder { return [self initWithUTF16String: string length: of_string_utf16_length(string) byteOrder: byteOrder]; } - (instancetype)initWithUTF16String: (const of_char16_t *)string length: (size_t)length byteOrder: (of_byte_order_t)byteOrder { OF_INVALID_INIT_METHOD } - (instancetype)initWithUTF32String: (const of_char32_t *)string { return [self initWithUTF32String: string length: of_string_utf32_length(string) byteOrder: OF_BYTE_ORDER_NATIVE]; } - (instancetype)initWithUTF32String: (const of_char32_t *)string length: (size_t)length { return [self initWithUTF32String: string length: length byteOrder: OF_BYTE_ORDER_NATIVE]; } - (instancetype)initWithUTF32String: (const of_char32_t *)string byteOrder: (of_byte_order_t)byteOrder { return [self initWithUTF32String: string length: of_string_utf32_length(string) byteOrder: byteOrder]; } - (instancetype)initWithUTF32String: (const of_char32_t *)string length: (size_t)length byteOrder: (of_byte_order_t)byteOrder { OF_INVALID_INIT_METHOD } - (instancetype)initWithFormat: (OFConstantString *)format, ... { id ret; va_list arguments; va_start(arguments, format); ret = [self initWithFormat: format arguments: arguments]; va_end(arguments); return ret; } - (instancetype)initWithFormat: (OFConstantString *)format arguments: (va_list)arguments { OF_INVALID_INIT_METHOD } #ifdef OF_HAVE_FILES - (instancetype)initWithContentsOfFile: (OFString *)path { return [self initWithContentsOfFile: path encoding: OF_STRING_ENCODING_UTF_8]; } - (instancetype)initWithContentsOfFile: (OFString *)path encoding: (of_string_encoding_t)encoding { char *tmp; unsigned long long fileSize; @try { void *pool = objc_autoreleasePoolPush(); OFFile *file = nil; @try { fileSize = [[OFFileManager defaultManager] attributesOfItemAtPath: path].fileSize; } @catch (OFRetrieveItemAttributesFailedException *e) { @throw [OFOpenItemFailedException exceptionWithPath: path mode: @"r" errNo: e.errNo]; } objc_autoreleasePoolPop(pool); # if ULLONG_MAX > SIZE_MAX if (fileSize > SIZE_MAX) @throw [OFOutOfRangeException exception]; #endif /* * We need one extra byte for the terminating zero if we want * to use -[initWithUTF8StringNoCopy:length:freeWhenDone:]. */ if (SIZE_MAX - (size_t)fileSize < 1) @throw [OFOutOfRangeException exception]; tmp = of_alloc((size_t)fileSize + 1, 1); @try { file = [[OFFile alloc] initWithPath: path mode: @"r"]; [file readIntoBuffer: tmp exactLength: (size_t)fileSize]; } @catch (id e) { free(tmp); @throw e; } @finally { [file release]; } tmp[(size_t)fileSize] = '\0'; } @catch (id e) { [self release]; @throw e; } if (encoding == OF_STRING_ENCODING_UTF_8) { @try { self = [self initWithUTF8StringNoCopy: tmp length: (size_t)fileSize freeWhenDone: true]; } @catch (id e) { free(tmp); @throw e; } } else { @try { self = [self initWithCString: tmp encoding: encoding length: (size_t)fileSize]; } @finally { free(tmp); } } return self; } #endif - (instancetype)initWithContentsOfURL: (OFURL *)URL { return [self initWithContentsOfURL: URL encoding: OF_STRING_ENCODING_AUTODETECT]; } - (instancetype)initWithContentsOfURL: (OFURL *)URL encoding: (of_string_encoding_t)encoding { void *pool = objc_autoreleasePoolPush(); OFData *data; @try { data = [OFData dataWithContentsOfURL: URL]; } @catch (id e) { [self release]; @throw e; } self = [self initWithCString: data.items encoding: encoding length: data.count * data.itemSize]; objc_autoreleasePoolPop(pool); return self; } - (instancetype)initWithSerialization: (OFXMLElement *)element { void *pool = objc_autoreleasePoolPush(); OFString *stringValue; @try { if (![element.namespace isEqual: OF_SERIALIZATION_NS]) @throw [OFInvalidArgumentException exception]; if ([self isKindOfClass: [OFMutableString class]]) { if (![element.name isEqual: @"OFMutableString"]) @throw [OFInvalidArgumentException exception]; } else { if (![element.name isEqual: @"OFString"]) @throw [OFInvalidArgumentException exception]; } stringValue = element.stringValue; } @catch (id e) { [self release]; @throw e; } self = [self initWithString: stringValue]; objc_autoreleasePoolPop(pool); return self; } - (size_t)of_getCString: (char *)cString maxLength: (size_t)maxLength encoding: (of_string_encoding_t)encoding lossy: (bool)lossy { const of_unichar_t *characters = self.characters; size_t i, length = self.length; switch (encoding) { case OF_STRING_ENCODING_UTF_8:; size_t j = 0; for (i = 0; i < length; i++) { char buffer[4]; size_t len = of_string_utf8_encode(characters[i], buffer); /* * Check for one more than the current index, as we * need one for the terminating zero. */ if (j + len >= maxLength) @throw [OFOutOfRangeException exception]; switch (len) { case 1: cString[j++] = buffer[0]; break; case 2: case 3: case 4: memcpy(cString + j, buffer, len); j += len; break; default: @throw [OFInvalidEncodingException exception]; break; } } cString[j] = '\0'; return j; case OF_STRING_ENCODING_ASCII: if (length + 1 > maxLength) @throw [OFOutOfRangeException exception]; for (i = 0; i < length; i++) { if OF_UNLIKELY (characters[i] > 0x80) { if (lossy) cString[i] = '?'; else @throw [OFInvalidEncodingException exception]; } else cString[i] = (unsigned char)characters[i]; } cString[i] = '\0'; return length; case OF_STRING_ENCODING_ISO_8859_1: if (length + 1 > maxLength) @throw [OFOutOfRangeException exception]; for (i = 0; i < length; i++) { if OF_UNLIKELY (characters[i] > 0xFF) { if (lossy) cString[i] = '?'; else @throw [OFInvalidEncodingException exception]; } else cString[i] = (unsigned char)characters[i]; } cString[i] = '\0'; return length; #ifdef HAVE_ISO_8859_2 case OF_STRING_ENCODING_ISO_8859_2: if (length + 1 > maxLength) @throw [OFOutOfRangeException exception]; if (!of_unicode_to_iso_8859_2(characters, (unsigned char *)cString, length, lossy)) @throw [OFInvalidEncodingException exception]; cString[length] = '\0'; return length; #endif #ifdef HAVE_ISO_8859_3 case OF_STRING_ENCODING_ISO_8859_3: if (length + 1 > maxLength) @throw [OFOutOfRangeException exception]; if (!of_unicode_to_iso_8859_3(characters, (unsigned char *)cString, length, lossy)) @throw [OFInvalidEncodingException exception]; cString[length] = '\0'; return length; #endif #ifdef HAVE_ISO_8859_15 case OF_STRING_ENCODING_ISO_8859_15: if (length + 1 > maxLength) @throw [OFOutOfRangeException exception]; if (!of_unicode_to_iso_8859_15(characters, (unsigned char *)cString, length, lossy)) @throw [OFInvalidEncodingException exception]; cString[length] = '\0'; return length; #endif #ifdef HAVE_WINDOWS_1251 case OF_STRING_ENCODING_WINDOWS_1251: if (length + 1 > maxLength) @throw [OFOutOfRangeException exception]; if (!of_unicode_to_windows_1251(characters, (unsigned char *)cString, length, lossy)) @throw [OFInvalidEncodingException exception]; cString[length] = '\0'; return length; #endif #ifdef HAVE_WINDOWS_1252 case OF_STRING_ENCODING_WINDOWS_1252: if (length + 1 > maxLength) @throw [OFOutOfRangeException exception]; if (!of_unicode_to_windows_1252(characters, (unsigned char *)cString, length, lossy)) @throw [OFInvalidEncodingException exception]; cString[length] = '\0'; return length; #endif #ifdef HAVE_CODEPAGE_437 case OF_STRING_ENCODING_CODEPAGE_437: if (length + 1 > maxLength) @throw [OFOutOfRangeException exception]; if (!of_unicode_to_codepage_437(characters, (unsigned char *)cString, length, lossy)) @throw [OFInvalidEncodingException exception]; cString[length] = '\0'; return length; #endif #ifdef HAVE_CODEPAGE_850 case OF_STRING_ENCODING_CODEPAGE_850: if (length + 1 > maxLength) @throw [OFOutOfRangeException exception]; if (!of_unicode_to_codepage_850(characters, (unsigned char *)cString, length, lossy)) @throw [OFInvalidEncodingException exception]; cString[length] = '\0'; return length; #endif #ifdef HAVE_CODEPAGE_858 case OF_STRING_ENCODING_CODEPAGE_858: if (length + 1 > maxLength) @throw [OFOutOfRangeException exception]; if (!of_unicode_to_codepage_858(characters, (unsigned char *)cString, length, lossy)) @throw [OFInvalidEncodingException exception]; cString[length] = '\0'; return length; #endif #ifdef HAVE_MAC_ROMAN case OF_STRING_ENCODING_MAC_ROMAN: if (length + 1 > maxLength) @throw [OFOutOfRangeException exception]; if (!of_unicode_to_mac_roman(characters, (unsigned char *)cString, length, lossy)) @throw [OFInvalidEncodingException exception]; cString[length] = '\0'; return length; #endif #ifdef HAVE_KOI8_R case OF_STRING_ENCODING_KOI8_R: if (length + 1 > maxLength) @throw [OFOutOfRangeException exception]; if (!of_unicode_to_koi8_r(characters, (unsigned char *)cString, length, lossy)) @throw [OFInvalidEncodingException exception]; cString[length] = '\0'; return length; #endif #ifdef HAVE_KOI8_U case OF_STRING_ENCODING_KOI8_U: if (length + 1 > maxLength) @throw [OFOutOfRangeException exception]; if (!of_unicode_to_koi8_u(characters, (unsigned char *)cString, length, lossy)) @throw [OFInvalidEncodingException exception]; cString[length] = '\0'; return length; #endif default: @throw [OFNotImplementedException exceptionWithSelector: _cmd object: self]; } } - (size_t)getCString: (char *)cString maxLength: (size_t)maxLength encoding: (of_string_encoding_t)encoding { return [self of_getCString: cString maxLength: maxLength encoding: encoding lossy: false]; } - (size_t)getLossyCString: (char *)cString maxLength: (size_t)maxLength encoding: (of_string_encoding_t)encoding { return [self of_getCString: cString maxLength: maxLength encoding: encoding lossy: true]; } - (const char *)of_cStringWithEncoding: (of_string_encoding_t)encoding lossy: (bool)lossy { size_t length = self.length; char *cString; size_t cStringLength; switch (encoding) { case OF_STRING_ENCODING_UTF_8: cString = of_alloc((length * 4) + 1, 1); @try { cStringLength = [self of_getCString: cString maxLength: (length * 4) + 1 encoding: OF_STRING_ENCODING_UTF_8 lossy: lossy]; } @catch (id e) { free(cString); @throw e; } @try { cString = of_realloc(cString, cStringLength + 1, 1); } @catch (OFOutOfMemoryException *e) { /* We don't care, as we only tried to make it smaller */ } break; case OF_STRING_ENCODING_ASCII: case OF_STRING_ENCODING_ISO_8859_1: case OF_STRING_ENCODING_ISO_8859_2: case OF_STRING_ENCODING_ISO_8859_3: case OF_STRING_ENCODING_ISO_8859_15: case OF_STRING_ENCODING_WINDOWS_1251: case OF_STRING_ENCODING_WINDOWS_1252: case OF_STRING_ENCODING_CODEPAGE_437: case OF_STRING_ENCODING_CODEPAGE_850: case OF_STRING_ENCODING_CODEPAGE_858: case OF_STRING_ENCODING_MAC_ROMAN: case OF_STRING_ENCODING_KOI8_R: case OF_STRING_ENCODING_KOI8_U: cString = of_alloc(length + 1, 1); @try { cStringLength = [self of_getCString: cString maxLength: length + 1 encoding: encoding lossy: lossy]; } @catch (id e) { free(cString); @throw e; } break; default: @throw [OFInvalidEncodingException exception]; } @try { return [[OFData dataWithItemsNoCopy: cString count: cStringLength + 1 freeWhenDone: true] items]; } @catch (id e) { free(cString); @throw e; } } - (const char *)cStringWithEncoding: (of_string_encoding_t)encoding { return [self of_cStringWithEncoding: encoding lossy: false]; } - (const char *)lossyCStringWithEncoding: (of_string_encoding_t)encoding { return [self of_cStringWithEncoding: encoding lossy: true]; } - (const char *)UTF8String { return [self cStringWithEncoding: OF_STRING_ENCODING_UTF_8]; } - (size_t)length { OF_UNRECOGNIZED_SELECTOR } - (size_t)cStringLengthWithEncoding: (of_string_encoding_t)encoding { switch (encoding) { case OF_STRING_ENCODING_UTF_8:; const of_unichar_t *characters; size_t length, UTF8StringLength = 0; characters = self.characters; length = self.length; for (size_t i = 0; i < length; i++) { char buffer[4]; size_t len = of_string_utf8_encode(characters[i], buffer); if (len == 0) @throw [OFInvalidEncodingException exception]; UTF8StringLength += len; } return UTF8StringLength; case OF_STRING_ENCODING_ASCII: case OF_STRING_ENCODING_ISO_8859_1: case OF_STRING_ENCODING_ISO_8859_2: case OF_STRING_ENCODING_ISO_8859_3: case OF_STRING_ENCODING_ISO_8859_15: case OF_STRING_ENCODING_WINDOWS_1251: case OF_STRING_ENCODING_WINDOWS_1252: case OF_STRING_ENCODING_CODEPAGE_437: case OF_STRING_ENCODING_CODEPAGE_850: case OF_STRING_ENCODING_CODEPAGE_858: case OF_STRING_ENCODING_MAC_ROMAN: case OF_STRING_ENCODING_KOI8_R: case OF_STRING_ENCODING_KOI8_U: return self.length; default: @throw [OFInvalidEncodingException exception]; } } - (size_t)UTF8StringLength { return [self cStringLengthWithEncoding: OF_STRING_ENCODING_UTF_8]; } - (of_unichar_t)characterAtIndex: (size_t)idx { OF_UNRECOGNIZED_SELECTOR } - (void)getCharacters: (of_unichar_t *)buffer inRange: (of_range_t)range { for (size_t i = 0; i < range.length; i++) buffer[i] = [self characterAtIndex: range.location + i]; } - (bool)isEqual: (id)object { void *pool; OFString *otherString; const of_unichar_t *characters, *otherCharacters; size_t length; if (object == self) return true; if (![object isKindOfClass: [OFString class]]) return false; otherString = object; length = self.length; if (otherString.length != length) return false; pool = objc_autoreleasePoolPush(); characters = self.characters; otherCharacters = otherString.characters; if (memcmp(characters, otherCharacters, length * sizeof(of_unichar_t)) != 0) { objc_autoreleasePoolPop(pool); return false; } objc_autoreleasePoolPop(pool); return true; } - (id)copy { return [self retain]; } - (id)mutableCopy { return [[OFMutableString alloc] initWithString: self]; } - (of_comparison_result_t)compare: (id )object { void *pool; OFString *otherString; const of_unichar_t *characters, *otherCharacters; size_t minimumLength; if (object == self) return OF_ORDERED_SAME; if (![(id)object isKindOfClass: [OFString class]]) @throw [OFInvalidArgumentException exception]; otherString = (OFString *)object; minimumLength = (self.length > otherString.length ? otherString.length : self.length); pool = objc_autoreleasePoolPush(); characters = self.characters; otherCharacters = otherString.characters; for (size_t i = 0; i < minimumLength; i++) { if (characters[i] > otherCharacters[i]) { objc_autoreleasePoolPop(pool); return OF_ORDERED_DESCENDING; } if (characters[i] < otherCharacters[i]) { objc_autoreleasePoolPop(pool); return OF_ORDERED_ASCENDING; } } objc_autoreleasePoolPop(pool); if (self.length > otherString.length) return OF_ORDERED_DESCENDING; if (self.length < otherString.length) return OF_ORDERED_ASCENDING; return OF_ORDERED_SAME; } - (of_comparison_result_t)caseInsensitiveCompare: (OFString *)otherString { void *pool = objc_autoreleasePoolPush(); const of_unichar_t *characters, *otherCharacters; size_t length, otherLength, minimumLength; if (otherString == self) return OF_ORDERED_SAME; characters = self.characters; otherCharacters = otherString.characters; length = self.length; otherLength = otherString.length; minimumLength = (length > otherLength ? otherLength : length); for (size_t i = 0; i < minimumLength; i++) { of_unichar_t c = characters[i]; of_unichar_t oc = otherCharacters[i]; #ifdef OF_HAVE_UNICODE_TABLES if (c >> 8 < OF_UNICODE_CASEFOLDING_TABLE_SIZE) { of_unichar_t tc = of_unicode_casefolding_table[c >> 8][c & 0xFF]; if (tc) c = tc; } if (oc >> 8 < OF_UNICODE_CASEFOLDING_TABLE_SIZE) { of_unichar_t tc = of_unicode_casefolding_table[oc >> 8][oc & 0xFF]; if (tc) oc = tc; } #else c = of_ascii_toupper(c); oc = of_ascii_toupper(oc); #endif if (c > oc) { objc_autoreleasePoolPop(pool); return OF_ORDERED_DESCENDING; } if (c < oc) { objc_autoreleasePoolPop(pool); return OF_ORDERED_ASCENDING; } } objc_autoreleasePoolPop(pool); if (length > otherLength) return OF_ORDERED_DESCENDING; if (length < otherLength) return OF_ORDERED_ASCENDING; return OF_ORDERED_SAME; } - (unsigned long)hash { const of_unichar_t *characters = self.characters; size_t length = self.length; uint32_t hash; OF_HASH_INIT(hash); for (size_t i = 0; i < length; i++) { const of_unichar_t c = characters[i]; OF_HASH_ADD(hash, (c & 0xFF0000) >> 16); OF_HASH_ADD(hash, (c & 0x00FF00) >> 8); OF_HASH_ADD(hash, c & 0x0000FF); } OF_HASH_FINALIZE(hash); return hash; } - (OFString *)description { return [[self copy] autorelease]; } - (OFXMLElement *)XMLElementBySerializing { void *pool = objc_autoreleasePoolPush(); OFXMLElement *element; OFString *className; if ([self isKindOfClass: [OFMutableString class]]) className = @"OFMutableString"; else className = @"OFString"; element = [OFXMLElement elementWithName: className namespace: OF_SERIALIZATION_NS stringValue: self]; [element retain]; objc_autoreleasePoolPop(pool); return [element autorelease]; } - (OFString *)JSONRepresentation { return [self of_JSONRepresentationWithOptions: 0 depth: 0]; } - (OFString *)JSONRepresentationWithOptions: (int)options { return [self of_JSONRepresentationWithOptions: options depth: 0]; } - (OFString *)of_JSONRepresentationWithOptions: (int)options depth: (size_t)depth { OFMutableString *JSON = [[self mutableCopy] autorelease]; /* FIXME: This is slow! Write it in pure C! */ [JSON replaceOccurrencesOfString: @"\\" withString: @"\\\\"]; [JSON replaceOccurrencesOfString: @"\"" withString: @"\\\""]; [JSON replaceOccurrencesOfString: @"\b" withString: @"\\b"]; [JSON replaceOccurrencesOfString: @"\f" withString: @"\\f"]; [JSON replaceOccurrencesOfString: @"\r" withString: @"\\r"]; [JSON replaceOccurrencesOfString: @"\t" withString: @"\\t"]; if (options & OF_JSON_REPRESENTATION_JSON5) { [JSON replaceOccurrencesOfString: @"\n" withString: @"\\\n"]; if (options & OF_JSON_REPRESENTATION_IDENTIFIER) { const char *cString = self.UTF8String; if ((!of_ascii_isalpha(cString[0]) && cString[0] != '_' && cString[0] != '$') || strpbrk(cString, " \n\r\t\b\f\\\"'") != NULL) { [JSON prependString: @"\""]; [JSON appendString: @"\""]; } } else { [JSON prependString: @"\""]; [JSON appendString: @"\""]; } } else { [JSON replaceOccurrencesOfString: @"\n" withString: @"\\n"]; [JSON prependString: @"\""]; [JSON appendString: @"\""]; } [JSON makeImmutable]; return JSON; } - (OFData *)messagePackRepresentation { OFMutableData *data; size_t length; length = self.UTF8StringLength; if (length <= 31) { uint8_t tmp = 0xA0 | ((uint8_t)length & 0x1F); data = [OFMutableData dataWithCapacity: length + 1]; [data addItem: &tmp]; } else if (length <= UINT8_MAX) { uint8_t type = 0xD9; uint8_t tmp = (uint8_t)length; data = [OFMutableData dataWithCapacity: length + 2]; [data addItem: &type]; [data addItem: &tmp]; } else if (length <= UINT16_MAX) { uint8_t type = 0xDA; uint16_t tmp = OF_BSWAP16_IF_LE((uint16_t)length); data = [OFMutableData dataWithCapacity: length + 3]; [data addItem: &type]; [data addItems: &tmp count: sizeof(tmp)]; } else if (length <= UINT32_MAX) { uint8_t type = 0xDB; uint32_t tmp = OF_BSWAP32_IF_LE((uint32_t)length); data = [OFMutableData dataWithCapacity: length + 5]; [data addItem: &type]; [data addItems: &tmp count: sizeof(tmp)]; } else @throw [OFOutOfRangeException exception]; [data addItems: self.UTF8String count: length]; return data; } - (of_range_t)rangeOfString: (OFString *)string { return [self rangeOfString: string options: 0 range: of_range(0, self.length)]; } - (of_range_t)rangeOfString: (OFString *)string options: (int)options { return [self rangeOfString: string options: options range: of_range(0, self.length)]; } - (of_range_t)rangeOfString: (OFString *)string options: (int)options range: (of_range_t)range { void *pool; const of_unichar_t *searchCharacters; of_unichar_t *characters; size_t searchLength; if ((searchLength = string.length) == 0) return of_range(0, 0); if (searchLength > range.length) return of_range(OF_NOT_FOUND, 0); if (range.length > SIZE_MAX / sizeof(of_unichar_t)) @throw [OFOutOfRangeException exception]; pool = objc_autoreleasePoolPush(); searchCharacters = string.characters; characters = of_alloc(range.length, sizeof(of_unichar_t)); @try { [self getCharacters: characters inRange: range]; if (options & OF_STRING_SEARCH_BACKWARDS) { for (size_t i = range.length - searchLength;; i--) { if (memcmp(characters + i, searchCharacters, searchLength * sizeof(of_unichar_t)) == 0) { objc_autoreleasePoolPop(pool); return of_range(range.location + i, searchLength); } /* No match and we're at the last character */ if (i == 0) break; } } else { for (size_t i = 0; i <= range.length - searchLength; i++) { if (memcmp(characters + i, searchCharacters, searchLength * sizeof(of_unichar_t)) == 0) { objc_autoreleasePoolPop(pool); return of_range(range.location + i, searchLength); } } } } @finally { free(characters); } objc_autoreleasePoolPop(pool); return of_range(OF_NOT_FOUND, 0); } - (size_t)indexOfCharacterFromSet: (OFCharacterSet *)characterSet { return [self indexOfCharacterFromSet: characterSet options: 0 range: of_range(0, self.length)]; } - (size_t)indexOfCharacterFromSet: (OFCharacterSet *)characterSet options: (int)options { return [self indexOfCharacterFromSet: characterSet options: options range: of_range(0, self.length)]; } - (size_t)indexOfCharacterFromSet: (OFCharacterSet *)characterSet options: (int)options range: (of_range_t)range { bool (*characterIsMember)(id, SEL, of_unichar_t) = (bool (*)(id, SEL, of_unichar_t))[characterSet methodForSelector: @selector(characterIsMember:)]; of_unichar_t *characters; if (range.length == 0) return OF_NOT_FOUND; if (range.length > SIZE_MAX / sizeof(of_unichar_t)) @throw [OFOutOfRangeException exception]; characters = of_alloc(range.length, sizeof(of_unichar_t)); @try { [self getCharacters: characters inRange: range]; if (options & OF_STRING_SEARCH_BACKWARDS) { for (size_t i = range.length - 1;; i--) { if (characterIsMember(characterSet, @selector(characterIsMember:), characters[i])) return range.location + i; /* No match and we're at the last character */ if (i == 0) break; } } else { for (size_t i = 0; i < range.length; i++) if (characterIsMember(characterSet, @selector(characterIsMember:), characters[i])) return range.location + i; } } @finally { free(characters); } return OF_NOT_FOUND; } - (bool)containsString: (OFString *)string { void *pool; const of_unichar_t *characters, *searchCharacters; size_t length, searchLength; if ((searchLength = string.length) == 0) return true; if (searchLength > (length = self.length)) return false; pool = objc_autoreleasePoolPush(); characters = self.characters; searchCharacters = string.characters; for (size_t i = 0; i <= length - searchLength; i++) { if (memcmp(characters + i, searchCharacters, searchLength * sizeof(of_unichar_t)) == 0) { objc_autoreleasePoolPop(pool); return true; } } objc_autoreleasePoolPop(pool); return false; } - (OFString *)substringFromIndex: (size_t)idx { return [self substringWithRange: of_range(idx, self.length - idx)]; } - (OFString *)substringToIndex: (size_t)idx { return [self substringWithRange: of_range(0, idx)]; } - (OFString *)substringWithRange: (of_range_t)range { void *pool; OFString *ret; if (range.length > SIZE_MAX - range.location || range.location + range.length > self.length) @throw [OFOutOfRangeException exception]; pool = objc_autoreleasePoolPush(); ret = [[OFString alloc] initWithCharacters: self.characters + range.location length: range.length]; objc_autoreleasePoolPop(pool); return [ret autorelease]; } - (OFString *)stringByAppendingString: (OFString *)string { OFMutableString *new; new = [OFMutableString stringWithString: self]; [new appendString: string]; [new makeImmutable]; return new; } - (OFString *)stringByAppendingFormat: (OFConstantString *)format, ... { OFString *ret; va_list arguments; va_start(arguments, format); ret = [self stringByAppendingFormat: format arguments: arguments]; va_end(arguments); return ret; } - (OFString *)stringByAppendingFormat: (OFConstantString *)format arguments: (va_list)arguments { OFMutableString *new = [OFMutableString stringWithString: self]; [new appendFormat: format arguments: arguments]; [new makeImmutable]; return new; } - (OFString *)stringByPrependingString: (OFString *)string { OFMutableString *new = [[string mutableCopy] autorelease]; [new appendString: self]; [new makeImmutable]; return new; } - (OFString *)stringByReplacingOccurrencesOfString: (OFString *)string withString: (OFString *)replacement { OFMutableString *new = [[self mutableCopy] autorelease]; [new replaceOccurrencesOfString: string withString: replacement]; [new makeImmutable]; return new; } - (OFString *)stringByReplacingOccurrencesOfString: (OFString *)string withString: (OFString *)replacement options: (int)options range: (of_range_t)range { OFMutableString *new = [[self mutableCopy] autorelease]; [new replaceOccurrencesOfString: string withString: replacement options: options range: range]; [new makeImmutable]; return new; } - (OFString *)uppercaseString { OFMutableString *new = [[self mutableCopy] autorelease]; [new uppercase]; [new makeImmutable]; return new; } - (OFString *)lowercaseString { OFMutableString *new = [[self mutableCopy] autorelease]; [new lowercase]; [new makeImmutable]; return new; } - (OFString *)capitalizedString { OFMutableString *new = [[self mutableCopy] autorelease]; [new capitalize]; [new makeImmutable]; return new; } - (OFString *)stringByDeletingLeadingWhitespaces { OFMutableString *new = [[self mutableCopy] autorelease]; [new deleteLeadingWhitespaces]; [new makeImmutable]; return new; } - (OFString *)stringByDeletingTrailingWhitespaces { OFMutableString *new = [[self mutableCopy] autorelease]; [new deleteTrailingWhitespaces]; [new makeImmutable]; return new; } - (OFString *)stringByDeletingEnclosingWhitespaces { OFMutableString *new = [[self mutableCopy] autorelease]; [new deleteEnclosingWhitespaces]; [new makeImmutable]; return new; } - (bool)hasPrefix: (OFString *)prefix { of_unichar_t *tmp; size_t prefixLength; bool hasPrefix; if ((prefixLength = prefix.length) > self.length) return false; tmp = of_alloc(prefixLength, sizeof(of_unichar_t)); @try { void *pool = objc_autoreleasePoolPush(); [self getCharacters: tmp inRange: of_range(0, prefixLength)]; hasPrefix = (memcmp(tmp, prefix.characters, prefixLength * sizeof(of_unichar_t)) == 0); objc_autoreleasePoolPop(pool); } @finally { free(tmp); } return hasPrefix; } - (bool)hasSuffix: (OFString *)suffix { of_unichar_t *tmp; const of_unichar_t *suffixCharacters; size_t length, suffixLength; bool hasSuffix; if ((suffixLength = suffix.length) > self.length) return false; length = self.length; tmp = of_alloc(suffixLength, sizeof(of_unichar_t)); @try { void *pool = objc_autoreleasePoolPush(); [self getCharacters: tmp inRange: of_range(length - suffixLength, suffixLength)]; suffixCharacters = suffix.characters; hasSuffix = (memcmp(tmp, suffixCharacters, suffixLength * sizeof(of_unichar_t)) == 0); objc_autoreleasePoolPop(pool); } @finally { free(tmp); } return hasSuffix; } - (OFArray *)componentsSeparatedByString: (OFString *)delimiter { return [self componentsSeparatedByString: delimiter options: 0]; } - (OFArray *)componentsSeparatedByString: (OFString *)delimiter options: (int)options { void *pool; OFMutableArray *array; const of_unichar_t *characters, *delimiterCharacters; bool skipEmpty = (options & OF_STRING_SKIP_EMPTY); size_t length = self.length; size_t delimiterLength = delimiter.length; size_t last; OFString *component; if (delimiter == nil) @throw [OFInvalidArgumentException exception]; if (delimiter.length == 0) return [OFArray arrayWithObject: self]; array = [OFMutableArray array]; pool = objc_autoreleasePoolPush(); characters = self.characters; delimiterCharacters = delimiter.characters; if (delimiterLength > length) { [array addObject: [[self copy] autorelease]]; [array makeImmutable]; objc_autoreleasePoolPop(pool); return array; } last = 0; for (size_t i = 0; i <= length - delimiterLength; i++) { if (memcmp(characters + i, delimiterCharacters, delimiterLength * sizeof(of_unichar_t)) != 0) continue; component = [self substringWithRange: of_range(last, i - last)]; if (!skipEmpty || component.length > 0) [array addObject: component]; i += delimiterLength - 1; last = i + 1; } component = [self substringWithRange: of_range(last, length - last)]; if (!skipEmpty || component.length > 0) [array addObject: component]; [array makeImmutable]; objc_autoreleasePoolPop(pool); return array; } - (OFArray *) componentsSeparatedByCharactersInSet: (OFCharacterSet *)characterSet { return [self componentsSeparatedByCharactersInSet: characterSet options: 0]; } - (OFArray *) componentsSeparatedByCharactersInSet: (OFCharacterSet *)characterSet options: (int)options { OFMutableArray *array = [OFMutableArray array]; void *pool = objc_autoreleasePoolPush(); bool skipEmpty = (options & OF_STRING_SKIP_EMPTY); const of_unichar_t *characters = self.characters; size_t length = self.length; bool (*characterIsMember)(id, SEL, of_unichar_t) = (bool (*)(id, SEL, of_unichar_t))[characterSet methodForSelector: @selector(characterIsMember:)]; size_t last; last = 0; for (size_t i = 0; i < length; i++) { if (characterIsMember(characterSet, @selector(characterIsMember:), characters[i])) { if (!skipEmpty || i != last) { OFString *component = [self substringWithRange: of_range(last, i - last)]; [array addObject: component]; } last = i + 1; } } if (!skipEmpty || length != last) { OFString *component = [self substringWithRange: of_range(last, length - last)]; [array addObject: component]; } [array makeImmutable]; objc_autoreleasePoolPop(pool); return array; } - (long long)longLongValue { return [self longLongValueWithBase: 10]; } - (long long)longLongValueWithBase: (int)base { void *pool = objc_autoreleasePoolPush(); const char *UTF8String = self.UTF8String; bool negative = false; long long value = 0; while (of_ascii_isspace(*UTF8String)) UTF8String++; switch (*UTF8String) { case '-': negative = true; case '+': UTF8String++; } if (UTF8String[0] == '0') { if (UTF8String[1] == 'x') { if (base == 0) base = 16; if (base != 16 || UTF8String[2] == '\0') @throw [OFInvalidFormatException exception]; UTF8String += 2; } else { if (base == 0) base = 8; UTF8String++; } } if (base == 0) base = 10; while (*UTF8String != '\0') { unsigned char c = of_ascii_toupper(*UTF8String++); if (c >= '0' && c <= '9') c -= '0'; else if (c >= 'A' && c <= 'Z') c -= ('A' - 10); else if (of_ascii_isspace(c)) { while (*UTF8String != '\0') if (!of_ascii_isspace(*UTF8String++)) @throw [OFInvalidFormatException exception]; break; } else @throw [OFInvalidFormatException exception]; if (c >= base) @throw [OFInvalidFormatException exception]; if (LLONG_MAX / base < value || LLONG_MAX - (value * base) < c) @throw [OFOutOfRangeException exception]; value = (value * base) + c; } if (negative) value *= -1; objc_autoreleasePoolPop(pool); return value; } - (unsigned long long)unsignedLongLongValue { return [self unsignedLongLongValueWithBase: 10]; } - (unsigned long long)unsignedLongLongValueWithBase: (int)base { void *pool = objc_autoreleasePoolPush(); const char *UTF8String = self.UTF8String; unsigned long long value = 0; while (of_ascii_isspace(*UTF8String)) UTF8String++; switch (*UTF8String) { case '-': @throw [OFInvalidFormatException exception]; case '+': UTF8String++; } if (UTF8String[0] == '0') { if (UTF8String[1] == 'x') { if (base == 0) base = 16; if (base != 16 || UTF8String[2] == '\0') @throw [OFInvalidFormatException exception]; UTF8String += 2; } else { if (base == 0) base = 8; UTF8String++; } } if (base == 0) base = 10; while (*UTF8String != '\0') { unsigned char c = of_ascii_toupper(*UTF8String++); if (c >= '0' && c <= '9') c -= '0'; else if (c >= 'A' && c <= 'Z') c -= ('A' - 10); else if (of_ascii_isspace(c)) { while (*UTF8String != '\0') if (!of_ascii_isspace(*UTF8String++)) @throw [OFInvalidFormatException exception]; break; } else @throw [OFInvalidFormatException exception]; if (c >= base) @throw [OFInvalidFormatException exception]; if (ULLONG_MAX / base < value || ULLONG_MAX - (value * base) < c) @throw [OFOutOfRangeException exception]; value = (value * base) + c; } objc_autoreleasePoolPop(pool); return value; } - (float)floatValue { void *pool = objc_autoreleasePoolPush(); OFString *stripped = self.stringByDeletingEnclosingWhitespaces; if ([stripped caseInsensitiveCompare: @"INF"] == OF_ORDERED_SAME || [stripped caseInsensitiveCompare: @"INFINITY"] == OF_ORDERED_SAME) return INFINITY; if ([stripped caseInsensitiveCompare: @"-INF"] == OF_ORDERED_SAME || [stripped caseInsensitiveCompare: @"-INFINITY"] == OF_ORDERED_SAME) return -INFINITY; if ([stripped caseInsensitiveCompare: @"NAN"] == OF_ORDERED_SAME) return NAN; if ([stripped caseInsensitiveCompare: @"-NAN"] == OF_ORDERED_SAME) return -NAN; #ifdef HAVE_STRTOF_L const char *UTF8String = self.UTF8String; #else /* * If we have no strtof_l, we have no other choice but to replace "." * with the locale's decimal point. */ OFString *decimalPoint = [OFLocale decimalPoint]; const char *UTF8String = [self stringByReplacingOccurrencesOfString: @"." withString: decimalPoint].UTF8String; #endif char *endPointer = NULL; float value; errno = 0; #ifdef HAVE_STRTOF_L value = strtof_l(UTF8String, &endPointer, cLocale); #else value = strtof(UTF8String, &endPointer); #endif if (value == HUGE_VALF && errno == ERANGE) @throw [OFOutOfRangeException exception]; /* Check if there are any invalid chars left */ if (endPointer != NULL) for (; *endPointer != '\0'; endPointer++) /* Use isspace since strtof uses the same. */ if (!isspace((unsigned char)*endPointer)) @throw [OFInvalidFormatException exception]; objc_autoreleasePoolPop(pool); return value; } - (double)doubleValue { void *pool = objc_autoreleasePoolPush(); OFString *stripped = self.stringByDeletingEnclosingWhitespaces; if ([stripped caseInsensitiveCompare: @"INF"] == OF_ORDERED_SAME || [stripped caseInsensitiveCompare: @"INFINITY"] == OF_ORDERED_SAME) return INFINITY; if ([stripped caseInsensitiveCompare: @"-INF"] == OF_ORDERED_SAME || [stripped caseInsensitiveCompare: @"-INFINITY"] == OF_ORDERED_SAME) return -INFINITY; if ([stripped caseInsensitiveCompare: @"NAN"] == OF_ORDERED_SAME) return NAN; if ([stripped caseInsensitiveCompare: @"-NAN"] == OF_ORDERED_SAME) return -NAN; #ifdef HAVE_STRTOD_L const char *UTF8String = self.UTF8String; #else /* * If we have no strtod_l, we have no other choice but to replace "." * with the locale's decimal point. */ OFString *decimalPoint = [OFLocale decimalPoint]; const char *UTF8String = [self stringByReplacingOccurrencesOfString: @"." withString: decimalPoint].UTF8String; #endif char *endPointer = NULL; double value; errno = 0; #ifdef HAVE_STRTOD_L value = strtod_l(UTF8String, &endPointer, cLocale); #else value = strtod(UTF8String, &endPointer); #endif if (value == HUGE_VAL && errno == ERANGE) @throw [OFOutOfRangeException exception]; /* Check if there are any invalid chars left */ if (endPointer != NULL) for (; *endPointer != '\0'; endPointer++) /* Use isspace since strtod uses the same. */ if (!isspace((unsigned char)*endPointer)) @throw [OFInvalidFormatException exception]; objc_autoreleasePoolPop(pool); return value; } - (const of_unichar_t *)characters { size_t length = self.length; of_unichar_t *buffer; buffer = of_alloc(length, sizeof(of_unichar_t)); @try { [self getCharacters: buffer inRange: of_range(0, length)]; return [[OFData dataWithItemsNoCopy: buffer count: length itemSize: sizeof(of_unichar_t) freeWhenDone: true] items]; } @catch (id e) { free(buffer); @throw e; } } - (const of_char16_t *)UTF16String { return [self UTF16StringWithByteOrder: OF_BYTE_ORDER_NATIVE]; } - (const of_char16_t *)UTF16StringWithByteOrder: (of_byte_order_t)byteOrder { void *pool = objc_autoreleasePoolPush(); const of_unichar_t *characters = self.characters; size_t length = self.length; of_char16_t *buffer; size_t j; bool swap = (byteOrder != OF_BYTE_ORDER_NATIVE); /* Allocate memory for the worst case */ buffer = of_alloc((length + 1) * 2, sizeof(of_char16_t)); j = 0; for (size_t i = 0; i < length; i++) { of_unichar_t c = characters[i]; if (c > 0x10FFFF) { free(buffer); @throw [OFInvalidEncodingException exception]; } if (swap) { if (c > 0xFFFF) { c -= 0x10000; buffer[j++] = OF_BSWAP16(0xD800 | (c >> 10)); buffer[j++] = OF_BSWAP16(0xDC00 | (c & 0x3FF)); } else buffer[j++] = OF_BSWAP16(c); } else { if (c > 0xFFFF) { c -= 0x10000; buffer[j++] = 0xD800 | (c >> 10); buffer[j++] = 0xDC00 | (c & 0x3FF); } else buffer[j++] = c; } } buffer[j] = 0; @try { buffer = of_realloc(buffer, j + 1, sizeof(of_char16_t)); } @catch (OFOutOfMemoryException *e) { /* We don't care, as we only tried to make it smaller */ } objc_autoreleasePoolPop(pool); @try { return [[OFData dataWithItemsNoCopy: buffer count: j + 1 itemSize: sizeof(of_char16_t) freeWhenDone: true] items]; } @catch (id e) { free(buffer); @throw e; } } - (size_t)UTF16StringLength { const of_unichar_t *characters = self.characters; size_t length, UTF16StringLength; length = UTF16StringLength = self.length; for (size_t i = 0; i < length; i++) if (characters[i] > 0xFFFF) UTF16StringLength++; return UTF16StringLength; } - (const of_char32_t *)UTF32String { return [self UTF32StringWithByteOrder: OF_BYTE_ORDER_NATIVE]; } - (const of_char32_t *)UTF32StringWithByteOrder: (of_byte_order_t)byteOrder { size_t length = self.length; of_char32_t *buffer; buffer = of_alloc(length + 1, sizeof(of_char32_t)); @try { [self getCharacters: buffer inRange: of_range(0, length)]; buffer[length] = 0; if (byteOrder != OF_BYTE_ORDER_NATIVE) for (size_t i = 0; i < length; i++) buffer[i] = OF_BSWAP32(buffer[i]); return [[OFData dataWithItemsNoCopy: buffer count: length + 1 itemSize: sizeof(of_char32_t) freeWhenDone: true] items]; } @catch (id e) { free(buffer); @throw e; } } - (OFData *)dataWithEncoding: (of_string_encoding_t)encoding { void *pool = objc_autoreleasePoolPush(); OFData *data = [OFData dataWithItems: [self cStringWithEncoding: encoding] count: [self cStringLengthWithEncoding: encoding]]; [data retain]; objc_autoreleasePoolPop(pool); return [data autorelease]; } #ifdef OF_HAVE_UNICODE_TABLES - (OFString *)decomposedStringWithCanonicalMapping { return decomposedString(self, of_unicode_decomposition_table, OF_UNICODE_DECOMPOSITION_TABLE_SIZE); } - (OFString *)decomposedStringWithCompatibilityMapping { return decomposedString(self, of_unicode_decomposition_compat_table, OF_UNICODE_DECOMPOSITION_COMPAT_TABLE_SIZE); } #endif #ifdef OF_WINDOWS - (OFString *)stringByExpandingWindowsEnvironmentStrings { if ([OFSystemInfo isWindowsNT]) { wchar_t buffer[512]; size_t length; if ((length = ExpandEnvironmentStringsW(self.UTF16String, buffer, sizeof(buffer))) == 0) return self; return [OFString stringWithUTF16String: buffer length: length - 1]; } else { of_string_encoding_t encoding = [OFLocale encoding]; char buffer[512]; size_t length; if ((length = ExpandEnvironmentStringsA( [self cStringWithEncoding: encoding], buffer, sizeof(buffer))) == 0) return self; return [OFString stringWithCString: buffer encoding: encoding length: length - 1]; } } #endif #ifdef OF_HAVE_FILES - (void)writeToFile: (OFString *)path { [self writeToFile: path encoding: OF_STRING_ENCODING_UTF_8]; } - (void)writeToFile: (OFString *)path encoding: (of_string_encoding_t)encoding { void *pool = objc_autoreleasePoolPush(); OFFile *file = [OFFile fileWithPath: path mode: @"w"]; [file writeString: self encoding: encoding]; objc_autoreleasePoolPop(pool); } #endif - (void)writeToURL: (OFURL *)URL { [self writeToURL: URL encoding: OF_STRING_ENCODING_UTF_8]; } - (void)writeToURL: (OFURL *)URL encoding: (of_string_encoding_t)encoding { void *pool = objc_autoreleasePoolPush(); OFURLHandler *URLHandler; OFStream *stream; if ((URLHandler = [OFURLHandler handlerForURL: URL]) == nil) @throw [OFUnsupportedProtocolException exceptionWithURL: URL]; stream = [URLHandler openItemAtURL: URL mode: @"w"]; [stream writeString: self encoding: encoding]; objc_autoreleasePoolPop(pool); } #ifdef OF_HAVE_BLOCKS - (void)enumerateLinesUsingBlock: (of_string_line_enumeration_block_t)block { void *pool = objc_autoreleasePoolPush(); const of_unichar_t *characters = self.characters; size_t i, last = 0, length = self.length; bool stop = false, lastCarriageReturn = false; for (i = 0; i < length && !stop; i++) { if (lastCarriageReturn && characters[i] == '\n') { lastCarriageReturn = false; last++; continue; } if (characters[i] == '\n' || characters[i] == '\r') { void *pool2 = objc_autoreleasePoolPush(); block([self substringWithRange: of_range(last, i - last)], &stop); last = i + 1; objc_autoreleasePoolPop(pool2); } lastCarriageReturn = (characters[i] == '\r'); } if (!stop) block([self substringWithRange: of_range(last, i - last)], &stop); objc_autoreleasePoolPop(pool); } #endif @end