/*
* Copyright (c) 2008-2021 Jonathan Schleifer <js@nil.im>
*
* 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 <ctype.h>
#include <errno.h>
#include <math.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#if defined(HAVE_STRTOF_L) || defined(HAVE_STRTOD_L)
# include <locale.h>
#endif
#ifdef HAVE_XLOCALE_H
# include <xlocale.h>
#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: (OFStringEncoding)encoding
lossy: (bool)lossy OF_DIRECT;
- (const char *)of_cStringWithEncoding: (OFStringEncoding)encoding
lossy: (bool)lossy OF_DIRECT;
- (OFString *)
of_JSONRepresentationWithOptions: (OFJSONRepresentationOptions)options
depth: (size_t)depth;
@end
@interface OFStringPlaceholder: OFString
@end
extern bool of_unicode_to_iso_8859_2(const OFUnichar *, unsigned char *,
size_t, bool);
extern bool of_unicode_to_iso_8859_3(const OFUnichar *, unsigned char *,
size_t, bool);
extern bool of_unicode_to_iso_8859_15(const OFUnichar *, unsigned char *,
size_t, bool);
extern bool of_unicode_to_windows_1251(const OFUnichar *, unsigned char *,
size_t, bool);
extern bool of_unicode_to_windows_1252(const OFUnichar *, unsigned char *,
size_t, bool);
extern bool of_unicode_to_codepage_437(const OFUnichar *, unsigned char *,
size_t, bool);
extern bool of_unicode_to_codepage_850(const OFUnichar *, unsigned char *,
size_t, bool);
extern bool of_unicode_to_codepage_858(const OFUnichar *, unsigned char *,
size_t, bool);
extern bool of_unicode_to_mac_roman(const OFUnichar *, unsigned char *,
size_t, bool);
extern bool of_unicode_to_koi8_r(const OFUnichar *, unsigned char *,
size_t, bool);
extern bool of_unicode_to_koi8_u(const OFUnichar *, unsigned char *,
size_t, bool);
/* References for static linking */
void
_references_to_categories_of_OFString(void)
{
_OFString_CryptographicHashing_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];
}
OFStringEncoding
OFStringEncodingParseName(OFString *string)
{
void *pool = objc_autoreleasePoolPush();
OFStringEncoding encoding;
string = string.lowercaseString;
if ([string isEqual: @"utf8"] || [string isEqual: @"utf-8"])
encoding = OFStringEncodingUTF8;
else if ([string isEqual: @"ascii"] || [string isEqual: @"us-ascii"])
encoding = OFStringEncodingASCII;
else if ([string isEqual: @"iso-8859-1"] ||
[string isEqual: @"iso_8859-1"])
encoding = OFStringEncodingISO8859_1;
else if ([string isEqual: @"iso-8859-2"] ||
[string isEqual: @"iso_8859-2"])
encoding = OFStringEncodingISO8859_2;
else if ([string isEqual: @"iso-8859-3"] ||
[string isEqual: @"iso_8859-3"])
encoding = OFStringEncodingISO8859_3;
else if ([string isEqual: @"iso-8859-15"] ||
[string isEqual: @"iso_8859-15"])
encoding = OFStringEncodingISO8859_15;
else if ([string isEqual: @"windows-1251"] ||
[string isEqual: @"cp1251"] || [string isEqual: @"cp-1251"] ||
[string isEqual: @"1251"])
encoding = OFStringEncodingWindows1251;
else if ([string isEqual: @"windows-1252"] ||
[string isEqual: @"cp1252"] || [string isEqual: @"cp-1252"] ||
[string isEqual: @"1252"])
encoding = OFStringEncodingWindows1252;
else if ([string isEqual: @"cp437"] || [string isEqual: @"cp-437"] ||
[string isEqual: @"ibm437"] || [string isEqual: @"437"])
encoding = OFStringEncodingCodepage437;
else if ([string isEqual: @"cp850"] || [string isEqual: @"cp-850"] ||
[string isEqual: @"ibm850"] || [string isEqual: @"850"])
encoding = OFStringEncodingCodepage850;
else if ([string isEqual: @"cp858"] || [string isEqual: @"cp-858"] ||
[string isEqual: @"ibm858"] || [string isEqual: @"858"])
encoding = OFStringEncodingCodepage858;
else if ([string isEqual: @"macintosh"] || [string isEqual: @"mac"])
encoding = OFStringEncodingMacRoman;
else if ([string isEqual: @"koi8-r"])
encoding = OFStringEncodingKOI8R;
else if ([string isEqual: @"koi8-u"])
encoding = OFStringEncodingKOI8U;
else
@throw [OFInvalidArgumentException exception];
objc_autoreleasePoolPop(pool);
return encoding;
}
OFString *
OFStringEncodingName(OFStringEncoding encoding)
{
switch (encoding) {
case OFStringEncodingUTF8:
return @"UTF-8";
case OFStringEncodingASCII:
return @"ASCII";
case OFStringEncodingISO8859_1:
return @"ISO 8859-1";
case OFStringEncodingISO8859_2:
return @"ISO 8859-2";
case OFStringEncodingISO8859_3:
return @"ISO 8859-3";
case OFStringEncodingISO8859_15:
return @"ISO 8859-15";
case OFStringEncodingWindows1251:
return @"Windows-1251";
case OFStringEncodingWindows1252:
return @"Windows-1252";
case OFStringEncodingCodepage437:
return @"Codepage 437";
case OFStringEncodingCodepage850:
return @"Codepage 850";
case OFStringEncodingCodepage858:
return @"Codepage 858";
case OFStringEncodingMacRoman:
return @"Mac Roman";
case OFStringEncodingKOI8R:
return @"KOI8-R";
case OFStringEncodingKOI8U:
return @"KOI8-U";
case OFStringEncodingAutodetect:
return @"autodetect";
}
return nil;
}
size_t
of_string_utf8_encode(OFUnichar 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, OFUnichar *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 OFChar16 *string)
{
size_t length = 0;
while (*string++ != 0)
length++;
return length;
}
size_t
of_string_utf32_length(const OFChar32 *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 OFUnichar *characters = self.characters;
size_t length = self.length;
for (size_t i = 0; i < length; i++) {
OFUnichar 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: (OFStringEncoding)encoding
{
if (encoding == OFStringEncodingUTF8) {
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: (OFStringEncoding)encoding
length: (size_t)cStringLength
{
if (encoding == OFStringEncodingUTF8) {
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: (OFStringEncoding)encoding
{
return (id)[[OFUTF8String alloc] initWithData: data
encoding: encoding];
}
- (instancetype)initWithString: (OFString *)string
{
return (id)[[OFUTF8String alloc] initWithString: string];
}
- (instancetype)initWithCharacters: (const OFUnichar *)string
length: (size_t)length
{
return (id)[[OFUTF8String alloc] initWithCharacters: string
length: length];
}
- (instancetype)initWithUTF16String: (const OFChar16 *)string
{
return (id)[[OFUTF8String alloc] initWithUTF16String: string];
}
- (instancetype)initWithUTF16String: (const OFChar16 *)string
length: (size_t)length
{
return (id)[[OFUTF8String alloc] initWithUTF16String: string
length: length];
}
- (instancetype)initWithUTF16String: (const OFChar16 *)string
byteOrder: (OFByteOrder)byteOrder
{
return (id)[[OFUTF8String alloc] initWithUTF16String: string
byteOrder: byteOrder];
}
- (instancetype)initWithUTF16String: (const OFChar16 *)string
length: (size_t)length
byteOrder: (OFByteOrder)byteOrder
{
return (id)[[OFUTF8String alloc] initWithUTF16String: string
length: length
byteOrder: byteOrder];
}
- (instancetype)initWithUTF32String: (const OFChar32 *)string
{
return (id)[[OFUTF8String alloc] initWithUTF32String: string];
}
- (instancetype)initWithUTF32String: (const OFChar32 *)string
length: (size_t)length
{
return (id)[[OFUTF8String alloc] initWithUTF32String: string
length: length];
}
- (instancetype)initWithUTF32String: (const OFChar32 *)string
byteOrder: (OFByteOrder)byteOrder
{
return (id)[[OFUTF8String alloc] initWithUTF32String: string
byteOrder: byteOrder];
}
- (instancetype)initWithUTF32String: (const OFChar32 *)string
length: (size_t)length
byteOrder: (OFByteOrder)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: (OFStringEncoding)encoding
{
return (id)[[OFUTF8String alloc] initWithContentsOfFile: path
encoding: encoding];
}
#endif
- (instancetype)initWithContentsOfURL: (OFURL *)URL
{
return (id)[[OFUTF8String alloc] initWithContentsOfURL: URL];
}
- (instancetype)initWithContentsOfURL: (OFURL *)URL
encoding: (OFStringEncoding)encoding
{
return (id)[[OFUTF8String alloc] initWithContentsOfURL: URL
encoding: encoding];
}
- (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: (OFStringEncoding)encoding
{
return [[[self alloc] initWithCString: cString
encoding: encoding] autorelease];
}
+ (instancetype)stringWithCString: (const char *)cString
encoding: (OFStringEncoding)encoding
length: (size_t)cStringLength
{
return [[[self alloc] initWithCString: cString
encoding: encoding
length: cStringLength] autorelease];
}
+ (instancetype)stringWithData: (OFData *)data
encoding: (OFStringEncoding)encoding
{
return [[[self alloc] initWithData: data
encoding: encoding] autorelease];
}
+ (instancetype)stringWithString: (OFString *)string
{
return [[[self alloc] initWithString: string] autorelease];
}
+ (instancetype)stringWithCharacters: (const OFUnichar *)string
length: (size_t)length
{
return [[[self alloc] initWithCharacters: string
length: length] autorelease];
}
+ (instancetype)stringWithUTF16String: (const OFChar16 *)string
{
return [[[self alloc] initWithUTF16String: string] autorelease];
}
+ (instancetype)stringWithUTF16String: (const OFChar16 *)string
length: (size_t)length
{
return [[[self alloc] initWithUTF16String: string
length: length] autorelease];
}
+ (instancetype)stringWithUTF16String: (const OFChar16 *)string
byteOrder: (OFByteOrder)byteOrder
{
return [[[self alloc] initWithUTF16String: string
byteOrder: byteOrder] autorelease];
}
+ (instancetype)stringWithUTF16String: (const OFChar16 *)string
length: (size_t)length
byteOrder: (OFByteOrder)byteOrder
{
return [[[self alloc] initWithUTF16String: string
length: length
byteOrder: byteOrder] autorelease];
}
+ (instancetype)stringWithUTF32String: (const OFChar32 *)string
{
return [[[self alloc] initWithUTF32String: string] autorelease];
}
+ (instancetype)stringWithUTF32String: (const OFChar32 *)string
length: (size_t)length
{
return [[[self alloc] initWithUTF32String: string
length: length] autorelease];
}
+ (instancetype)stringWithUTF32String: (const OFChar32 *)string
byteOrder: (OFByteOrder)byteOrder
{
return [[[self alloc] initWithUTF32String: string
byteOrder: byteOrder] autorelease];
}
+ (instancetype)stringWithUTF32String: (const OFChar32 *)string
length: (size_t)length
byteOrder: (OFByteOrder)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: (OFStringEncoding)encoding
{
return [[[self alloc] initWithContentsOfFile: path
encoding: encoding] autorelease];
}
#endif
+ (instancetype)stringWithContentsOfURL: (OFURL *)URL
{
return [[[self alloc] initWithContentsOfURL: URL] autorelease];
}
+ (instancetype)stringWithContentsOfURL: (OFURL *)URL
encoding: (OFStringEncoding)encoding
{
return [[[self alloc] initWithContentsOfURL: URL
encoding: encoding] autorelease];
}
- (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: OFStringEncodingUTF8
length: strlen(UTF8String)];
}
- (instancetype)initWithUTF8String: (const char *)UTF8String
length: (size_t)UTF8StringLength
{
return [self initWithCString: UTF8String
encoding: OFStringEncodingUTF8
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: (OFStringEncoding)encoding
{
return [self initWithCString: cString
encoding: encoding
length: strlen(cString)];
}
- (instancetype)initWithCString: (const char *)cString
encoding: (OFStringEncoding)encoding
length: (size_t)cStringLength
{
OF_INVALID_INIT_METHOD
}
- (instancetype)initWithData: (OFData *)data
encoding: (OFStringEncoding)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 OFUnichar *)string
length: (size_t)length
{
OF_INVALID_INIT_METHOD
}
- (instancetype)initWithUTF16String: (const OFChar16 *)string
{
return [self initWithUTF16String: string
length: of_string_utf16_length(string)
byteOrder: OFByteOrderNative];
}
- (instancetype)initWithUTF16String: (const OFChar16 *)string
length: (size_t)length
{
return [self initWithUTF16String: string
length: length
byteOrder: OFByteOrderNative];
}
- (instancetype)initWithUTF16String: (const OFChar16 *)string
byteOrder: (OFByteOrder)byteOrder
{
return [self initWithUTF16String: string
length: of_string_utf16_length(string)
byteOrder: byteOrder];
}
- (instancetype)initWithUTF16String: (const OFChar16 *)string
length: (size_t)length
byteOrder: (OFByteOrder)byteOrder
{
OF_INVALID_INIT_METHOD
}
- (instancetype)initWithUTF32String: (const OFChar32 *)string
{
return [self initWithUTF32String: string
length: of_string_utf32_length(string)
byteOrder: OFByteOrderNative];
}
- (instancetype)initWithUTF32String: (const OFChar32 *)string
length: (size_t)length
{
return [self initWithUTF32String: string
length: length
byteOrder: OFByteOrderNative];
}
- (instancetype)initWithUTF32String: (const OFChar32 *)string
byteOrder: (OFByteOrder)byteOrder
{
return [self initWithUTF32String: string
length: of_string_utf32_length(string)
byteOrder: byteOrder];
}
- (instancetype)initWithUTF32String: (const OFChar32 *)string
length: (size_t)length
byteOrder: (OFByteOrder)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: OFStringEncodingUTF8];
}
- (instancetype)initWithContentsOfFile: (OFString *)path
encoding: (OFStringEncoding)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 == OFStringEncodingUTF8) {
@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: OFStringEncodingAutodetect];
}
- (instancetype)initWithContentsOfURL: (OFURL *)URL
encoding: (OFStringEncoding)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: (OFStringEncoding)encoding
lossy: (bool)lossy
{
const OFUnichar *characters = self.characters;
size_t i, length = self.length;
switch (encoding) {
case OFStringEncodingUTF8:;
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 OFStringEncodingASCII:
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 OFStringEncodingISO8859_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 OFStringEncodingISO8859_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 OFStringEncodingISO8859_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 OFStringEncodingISO8859_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 OFStringEncodingWindows1251:
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 OFStringEncodingWindows1252:
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 OFStringEncodingCodepage437:
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 OFStringEncodingCodepage850:
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 OFStringEncodingCodepage858:
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 OFStringEncodingMacRoman:
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 OFStringEncodingKOI8R:
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 OFStringEncodingKOI8U:
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: (OFStringEncoding)encoding
{
return [self of_getCString: cString
maxLength: maxLength
encoding: encoding
lossy: false];
}
- (size_t)getLossyCString: (char *)cString
maxLength: (size_t)maxLength
encoding: (OFStringEncoding)encoding
{
return [self of_getCString: cString
maxLength: maxLength
encoding: encoding
lossy: true];
}
- (const char *)of_cStringWithEncoding: (OFStringEncoding)encoding
lossy: (bool)lossy
{
size_t length = self.length;
char *cString;
size_t cStringLength;
switch (encoding) {
case OFStringEncodingUTF8:
cString = of_alloc((length * 4) + 1, 1);
@try {
cStringLength = [self
of_getCString: cString
maxLength: (length * 4) + 1
encoding: OFStringEncodingUTF8
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 OFStringEncodingASCII:
case OFStringEncodingISO8859_1:
case OFStringEncodingISO8859_2:
case OFStringEncodingISO8859_3:
case OFStringEncodingISO8859_15:
case OFStringEncodingWindows1251:
case OFStringEncodingWindows1252:
case OFStringEncodingCodepage437:
case OFStringEncodingCodepage850:
case OFStringEncodingCodepage858:
case OFStringEncodingMacRoman:
case OFStringEncodingKOI8R:
case OFStringEncodingKOI8U:
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: (OFStringEncoding)encoding
{
return [self of_cStringWithEncoding: encoding lossy: false];
}
- (const char *)lossyCStringWithEncoding: (OFStringEncoding)encoding
{
return [self of_cStringWithEncoding: encoding lossy: true];
}
- (const char *)UTF8String
{
return [self cStringWithEncoding: OFStringEncodingUTF8];
}
- (size_t)length
{
OF_UNRECOGNIZED_SELECTOR
}
- (size_t)cStringLengthWithEncoding: (OFStringEncoding)encoding
{
switch (encoding) {
case OFStringEncodingUTF8:;
const OFUnichar *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 OFStringEncodingASCII:
case OFStringEncodingISO8859_1:
case OFStringEncodingISO8859_2:
case OFStringEncodingISO8859_3:
case OFStringEncodingISO8859_15:
case OFStringEncodingWindows1251:
case OFStringEncodingWindows1252:
case OFStringEncodingCodepage437:
case OFStringEncodingCodepage850:
case OFStringEncodingCodepage858:
case OFStringEncodingMacRoman:
case OFStringEncodingKOI8R:
case OFStringEncodingKOI8U:
return self.length;
default:
@throw [OFInvalidEncodingException exception];
}
}
- (size_t)UTF8StringLength
{
return [self cStringLengthWithEncoding: OFStringEncodingUTF8];
}
- (OFUnichar)characterAtIndex: (size_t)idx
{
OF_UNRECOGNIZED_SELECTOR
}
- (void)getCharacters: (OFUnichar *)buffer
inRange: (OFRange)range
{
for (size_t i = 0; i < range.length; i++)
buffer[i] = [self characterAtIndex: range.location + i];
}
- (bool)isEqual: (id)object
{
void *pool;
OFString *string;
const OFUnichar *characters, *otherCharacters;
size_t length;
if (object == self)
return true;
if (![object isKindOfClass: [OFString class]])
return false;
string = object;
length = self.length;
if (string.length != length)
return false;
pool = objc_autoreleasePoolPush();
characters = self.characters;
otherCharacters = string.characters;
if (memcmp(characters, otherCharacters,
length * sizeof(OFUnichar)) != 0) {
objc_autoreleasePoolPop(pool);
return false;
}
objc_autoreleasePoolPop(pool);
return true;
}
- (id)copy
{
return [self retain];
}
- (id)mutableCopy
{
return [[OFMutableString alloc] initWithString: self];
}
- (OFComparisonResult)compare: (OFString *)string
{
void *pool;
const OFUnichar *characters, *otherCharacters;
size_t minimumLength;
if (string == self)
return OFOrderedSame;
if (![string isKindOfClass: [OFString class]])
@throw [OFInvalidArgumentException exception];
minimumLength = (self.length > string.length
? string.length : self.length);
pool = objc_autoreleasePoolPush();
characters = self.characters;
otherCharacters = string.characters;
for (size_t i = 0; i < minimumLength; i++) {
if (characters[i] > otherCharacters[i]) {
objc_autoreleasePoolPop(pool);
return OFOrderedDescending;
}
if (characters[i] < otherCharacters[i]) {
objc_autoreleasePoolPop(pool);
return OFOrderedAscending;
}
}
objc_autoreleasePoolPop(pool);
if (self.length > string.length)
return OFOrderedDescending;
if (self.length < string.length)
return OFOrderedAscending;
return OFOrderedSame;
}
- (OFComparisonResult)caseInsensitiveCompare: (OFString *)string
{
void *pool = objc_autoreleasePoolPush();
const OFUnichar *characters, *otherCharacters;
size_t length, otherLength, minimumLength;
if (string == self)
return OFOrderedSame;
characters = self.characters;
otherCharacters = string.characters;
length = self.length;
otherLength = string.length;
minimumLength = (length > otherLength ? otherLength : length);
for (size_t i = 0; i < minimumLength; i++) {
OFUnichar c = characters[i];
OFUnichar oc = otherCharacters[i];
#ifdef OF_HAVE_UNICODE_TABLES
if (c >> 8 < OF_UNICODE_CASEFOLDING_TABLE_SIZE) {
OFUnichar tc =
of_unicode_casefolding_table[c >> 8][c & 0xFF];
if (tc)
c = tc;
}
if (oc >> 8 < OF_UNICODE_CASEFOLDING_TABLE_SIZE) {
OFUnichar 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 OFOrderedDescending;
}
if (c < oc) {
objc_autoreleasePoolPop(pool);
return OFOrderedAscending;
}
}
objc_autoreleasePoolPop(pool);
if (length > otherLength)
return OFOrderedDescending;
if (length < otherLength)
return OFOrderedAscending;
return OFOrderedSame;
}
- (unsigned long)hash
{
const OFUnichar *characters = self.characters;
size_t length = self.length;
uint32_t hash;
OF_HASH_INIT(hash);
for (size_t i = 0; i < length; i++) {
const OFUnichar 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:
(OFJSONRepresentationOptions)options
{
return [self of_JSONRepresentationWithOptions: options depth: 0];
}
- (OFString *)
of_JSONRepresentationWithOptions: (OFJSONRepresentationOptions)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 & OFJSONRepresentationOptionJSON5) {
[JSON replaceOccurrencesOfString: @"\n" withString: @"\\\n"];
if (options & OFJSONRepresentationOptionIsIdentifier) {
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;
}
- (OFRange)rangeOfString: (OFString *)string
{
return [self rangeOfString: string
options: 0
range: OFRangeMake(0, self.length)];
}
- (OFRange)rangeOfString: (OFString *)string
options: (OFStringSearchOptions)options
{
return [self rangeOfString: string
options: options
range: OFRangeMake(0, self.length)];
}
- (OFRange)rangeOfString: (OFString *)string
options: (OFStringSearchOptions)options
range: (OFRange)range
{
void *pool;
const OFUnichar *searchCharacters;
OFUnichar *characters;
size_t searchLength;
if ((searchLength = string.length) == 0)
return OFRangeMake(0, 0);
if (searchLength > range.length)
return OFRangeMake(OFNotFound, 0);
if (range.length > SIZE_MAX / sizeof(OFUnichar))
@throw [OFOutOfRangeException exception];
pool = objc_autoreleasePoolPush();
searchCharacters = string.characters;
characters = of_alloc(range.length, sizeof(OFUnichar));
@try {
[self getCharacters: characters inRange: range];
if (options & OFStringSearchBackwards) {
for (size_t i = range.length - searchLength;; i--) {
if (memcmp(characters + i, searchCharacters,
searchLength * sizeof(OFUnichar)) == 0) {
objc_autoreleasePoolPop(pool);
return OFRangeMake(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(OFUnichar)) == 0) {
objc_autoreleasePoolPop(pool);
return OFRangeMake(range.location + i,
searchLength);
}
}
}
} @finally {
free(characters);
}
objc_autoreleasePoolPop(pool);
return OFRangeMake(OFNotFound, 0);
}
- (size_t)indexOfCharacterFromSet: (OFCharacterSet *)characterSet
{
return [self indexOfCharacterFromSet: characterSet
options: 0
range: OFRangeMake(0, self.length)];
}
- (size_t)indexOfCharacterFromSet: (OFCharacterSet *)characterSet
options: (OFStringSearchOptions)options
{
return [self indexOfCharacterFromSet: characterSet
options: options
range: OFRangeMake(0, self.length)];
}
- (size_t)indexOfCharacterFromSet: (OFCharacterSet *)characterSet
options: (OFStringSearchOptions)options
range: (OFRange)range
{
bool (*characterIsMember)(id, SEL, OFUnichar) =
(bool (*)(id, SEL, OFUnichar))[characterSet
methodForSelector: @selector(characterIsMember:)];
OFUnichar *characters;
if (range.length == 0)
return OFNotFound;
if (range.length > SIZE_MAX / sizeof(OFUnichar))
@throw [OFOutOfRangeException exception];
characters = of_alloc(range.length, sizeof(OFUnichar));
@try {
[self getCharacters: characters inRange: range];
if (options & OFStringSearchBackwards) {
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 OFNotFound;
}
- (bool)containsString: (OFString *)string
{
void *pool;
const OFUnichar *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(OFUnichar)) == 0) {
objc_autoreleasePoolPop(pool);
return true;
}
}
objc_autoreleasePoolPop(pool);
return false;
}
- (OFString *)substringFromIndex: (size_t)idx
{
return [self substringWithRange: OFRangeMake(idx, self.length - idx)];
}
- (OFString *)substringToIndex: (size_t)idx
{
return [self substringWithRange: OFRangeMake(0, idx)];
}
- (OFString *)substringWithRange: (OFRange)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: (OFRange)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
{
OFUnichar *tmp;
size_t prefixLength;
bool hasPrefix;
if ((prefixLength = prefix.length) > self.length)
return false;
tmp = of_alloc(prefixLength, sizeof(OFUnichar));
@try {
void *pool = objc_autoreleasePoolPush();
[self getCharacters: tmp inRange: OFRangeMake(0, prefixLength)];
hasPrefix = (memcmp(tmp, prefix.characters,
prefixLength * sizeof(OFUnichar)) == 0);
objc_autoreleasePoolPop(pool);
} @finally {
free(tmp);
}
return hasPrefix;
}
- (bool)hasSuffix: (OFString *)suffix
{
OFUnichar *tmp;
const OFUnichar *suffixCharacters;
size_t length, suffixLength;
bool hasSuffix;
if ((suffixLength = suffix.length) > self.length)
return false;
length = self.length;
tmp = of_alloc(suffixLength, sizeof(OFUnichar));
@try {
void *pool = objc_autoreleasePoolPush();
[self getCharacters: tmp
inRange: OFRangeMake(length - suffixLength,
suffixLength)];
suffixCharacters = suffix.characters;
hasSuffix = (memcmp(tmp, suffixCharacters,
suffixLength * sizeof(OFUnichar)) == 0);
objc_autoreleasePoolPop(pool);
} @finally {
free(tmp);
}
return hasSuffix;
}
- (OFArray *)componentsSeparatedByString: (OFString *)delimiter
{
return [self componentsSeparatedByString: delimiter options: 0];
}
- (OFArray *)componentsSeparatedByString: (OFString *)delimiter
options: (OFStringSeparationOptions)options
{
void *pool;
OFMutableArray *array;
const OFUnichar *characters, *delimiterCharacters;
bool skipEmpty = (options & OFStringSkipEmptyComponents);
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(OFUnichar)) != 0)
continue;
component = [self substringWithRange:
OFRangeMake(last, i - last)];
if (!skipEmpty || component.length > 0)
[array addObject: component];
i += delimiterLength - 1;
last = i + 1;
}
component = [self substringWithRange: OFRangeMake(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: (OFStringSeparationOptions)options
{
OFMutableArray *array = [OFMutableArray array];
void *pool = objc_autoreleasePoolPush();
bool skipEmpty = (options & OFStringSkipEmptyComponents);
const OFUnichar *characters = self.characters;
size_t length = self.length;
bool (*characterIsMember)(id, SEL, OFUnichar) =
(bool (*)(id, SEL, OFUnichar))[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:
OFRangeMake(last, i - last)];
[array addObject: component];
}
last = i + 1;
}
}
if (!skipEmpty || length != last) {
OFString *component = [self substringWithRange:
OFRangeMake(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"] == OFOrderedSame ||
[stripped caseInsensitiveCompare: @"INFINITY"] == OFOrderedSame)
return INFINITY;
if ([stripped caseInsensitiveCompare: @"-INF"] == OFOrderedSame ||
[stripped caseInsensitiveCompare: @"-INFINITY"] == OFOrderedSame)
return -INFINITY;
if ([stripped caseInsensitiveCompare: @"NAN"] == OFOrderedSame)
return NAN;
if ([stripped caseInsensitiveCompare: @"-NAN"] == OFOrderedSame)
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"] == OFOrderedSame ||
[stripped caseInsensitiveCompare: @"INFINITY"] == OFOrderedSame)
return INFINITY;
if ([stripped caseInsensitiveCompare: @"-INF"] == OFOrderedSame ||
[stripped caseInsensitiveCompare: @"-INFINITY"] == OFOrderedSame)
return -INFINITY;
if ([stripped caseInsensitiveCompare: @"NAN"] == OFOrderedSame)
return NAN;
if ([stripped caseInsensitiveCompare: @"-NAN"] == OFOrderedSame)
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 OFUnichar *)characters
{
size_t length = self.length;
OFUnichar *buffer;
buffer = of_alloc(length, sizeof(OFUnichar));
@try {
[self getCharacters: buffer inRange: OFRangeMake(0, length)];
return [[OFData dataWithItemsNoCopy: buffer
count: length
itemSize: sizeof(OFUnichar)
freeWhenDone: true] items];
} @catch (id e) {
free(buffer);
@throw e;
}
}
- (const OFChar16 *)UTF16String
{
return [self UTF16StringWithByteOrder: OFByteOrderNative];
}
- (const OFChar16 *)UTF16StringWithByteOrder: (OFByteOrder)byteOrder
{
void *pool = objc_autoreleasePoolPush();
const OFUnichar *characters = self.characters;
size_t length = self.length;
OFChar16 *buffer;
size_t j;
bool swap = (byteOrder != OFByteOrderNative);
/* Allocate memory for the worst case */
buffer = of_alloc((length + 1) * 2, sizeof(OFChar16));
j = 0;
for (size_t i = 0; i < length; i++) {
OFUnichar 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(OFChar16));
} @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(OFChar16)
freeWhenDone: true] items];
} @catch (id e) {
free(buffer);
@throw e;
}
}
- (size_t)UTF16StringLength
{
const OFUnichar *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 OFChar32 *)UTF32String
{
return [self UTF32StringWithByteOrder: OFByteOrderNative];
}
- (const OFChar32 *)UTF32StringWithByteOrder: (OFByteOrder)byteOrder
{
size_t length = self.length;
OFChar32 *buffer;
buffer = of_alloc(length + 1, sizeof(OFChar32));
@try {
[self getCharacters: buffer inRange: OFRangeMake(0, length)];
buffer[length] = 0;
if (byteOrder != OFByteOrderNative)
for (size_t i = 0; i < length; i++)
buffer[i] = OF_BSWAP32(buffer[i]);
return [[OFData dataWithItemsNoCopy: buffer
count: length + 1
itemSize: sizeof(OFChar32)
freeWhenDone: true] items];
} @catch (id e) {
free(buffer);
@throw e;
}
}
- (OFData *)dataWithEncoding: (OFStringEncoding)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 {
OFStringEncoding 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: OFStringEncodingUTF8];
}
- (void)writeToFile: (OFString *)path encoding: (OFStringEncoding)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: OFStringEncodingUTF8];
}
- (void)writeToURL: (OFURL *)URL encoding: (OFStringEncoding)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: (OFStringLineEnumerationBlock)block
{
void *pool = objc_autoreleasePoolPush();
const OFUnichar *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:
OFRangeMake(last, i - last)], &stop);
last = i + 1;
objc_autoreleasePoolPop(pool2);
}
lastCarriageReturn = (characters[i] == '\r');
}
if (!stop)
block([self substringWithRange: OFRangeMake(last, i - last)],
&stop);
objc_autoreleasePoolPop(pool);
}
#endif
@end