/* * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 * Jonathan Schleifer <js@heap.zone> * * 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 <stdarg.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #import "OFString.h" #import "OFMutableString_UTF8.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFOutOfRangeException.h" #import "of_asprintf.h" #import "unicode.h" static struct { Class isa; } placeholder; #ifndef OF_HAVE_UNICODE_TABLES /* Wrap these, as they can be macros. */ static int toupperWrapper(int c) { return toupper(c); } static int tolowerWrapper(int c) { return tolower(c); } #endif @interface OFMutableString_placeholder: OFMutableString @end @implementation OFMutableString_placeholder - init { return (id)[[OFMutableString_UTF8 alloc] init]; } - initWithUTF8String: (const char*)UTF8String { return (id)[[OFMutableString_UTF8 alloc] initWithUTF8String: UTF8String]; } - initWithUTF8String: (const char*)UTF8String length: (size_t)UTF8StringLength { return (id)[[OFMutableString_UTF8 alloc] initWithUTF8String: UTF8String length: UTF8StringLength]; } - initWithCString: (const char*)cString encoding: (of_string_encoding_t)encoding { return (id)[[OFMutableString_UTF8 alloc] initWithCString: cString encoding: encoding]; } - initWithCString: (const char*)cString encoding: (of_string_encoding_t)encoding length: (size_t)cStringLength { return (id)[[OFMutableString_UTF8 alloc] initWithCString: cString encoding: encoding length: cStringLength]; } - initWithString: (OFString*)string { return (id)[[OFMutableString_UTF8 alloc] initWithString: string]; } - initWithCharacters: (const of_unichar_t*)characters length: (size_t)length { return (id)[[OFMutableString_UTF8 alloc] initWithCharacters: characters length: length]; } - initWithUTF16String: (const of_char16_t*)string { return (id)[[OFMutableString_UTF8 alloc] initWithUTF16String: string]; } - initWithUTF16String: (const of_char16_t*)string length: (size_t)length { return (id)[[OFMutableString_UTF8 alloc] initWithUTF16String: string length: length]; } - initWithUTF16String: (const of_char16_t*)string byteOrder: (of_byte_order_t)byteOrder { return (id)[[OFMutableString_UTF8 alloc] initWithUTF16String: string byteOrder: byteOrder]; } - initWithUTF16String: (const of_char16_t*)string length: (size_t)length byteOrder: (of_byte_order_t)byteOrder { return (id)[[OFMutableString_UTF8 alloc] initWithUTF16String: string length: length byteOrder: byteOrder]; } - initWithUTF32String: (const of_char32_t*)string { return (id)[[OFMutableString_UTF8 alloc] initWithUTF32String: string]; } - initWithUTF32String: (const of_char32_t*)string length: (size_t)length { return (id)[[OFMutableString_UTF8 alloc] initWithUTF32String: string length: length]; } - initWithUTF32String: (const of_char32_t*)string byteOrder: (of_byte_order_t)byteOrder { return (id)[[OFMutableString_UTF8 alloc] initWithUTF32String: string byteOrder: byteOrder]; } - initWithUTF32String: (const of_char32_t*)string length: (size_t)length byteOrder: (of_byte_order_t)byteOrder { return (id)[[OFMutableString_UTF8 alloc] initWithUTF32String: string length: length byteOrder: byteOrder]; } - initWithFormat: (OFConstantString*)format, ... { id ret; va_list arguments; va_start(arguments, format); ret = [[OFMutableString_UTF8 alloc] initWithFormat: format arguments: arguments]; va_end(arguments); return ret; } - initWithFormat: (OFConstantString*)format arguments: (va_list)arguments { return (id)[[OFMutableString_UTF8 alloc] initWithFormat: format arguments: arguments]; } #ifdef OF_HAVE_FILES - initWithContentsOfFile: (OFString*)path { return (id)[[OFMutableString_UTF8 alloc] initWithContentsOfFile: path]; } - initWithContentsOfFile: (OFString*)path encoding: (of_string_encoding_t)encoding { return (id)[[OFMutableString_UTF8 alloc] initWithContentsOfFile: path encoding: encoding]; } #endif - initWithContentsOfURL: (OFURL*)URL { return (id)[[OFMutableString_UTF8 alloc] initWithContentsOfURL: URL]; } - initWithContentsOfURL: (OFURL*)URL encoding: (of_string_encoding_t)encoding { return (id)[[OFMutableString_UTF8 alloc] initWithContentsOfURL: URL encoding: encoding]; } - initWithSerialization: (OFXMLElement*)element { return (id)[[OFMutableString_UTF8 alloc] initWithSerialization: element]; } - retain { return self; } - autorelease { return self; } - (void)release { } - (void)dealloc { OF_DEALLOC_UNSUPPORTED } @end @implementation OFMutableString + (void)initialize { if (self == [OFMutableString class]) placeholder.isa = [OFMutableString_placeholder class]; } + alloc { if (self == [OFMutableString class]) return (id)&placeholder; return [super alloc]; } #ifdef OF_HAVE_UNICODE_TABLES - (void)OF_convertWithWordStartTable: (const of_unichar_t *const[])startTable wordMiddleTable: (const of_unichar_t *const[])middleTable wordStartTableSize: (size_t)startTableSize wordMiddleTableSize: (size_t)middleTableSize { void *pool = objc_autoreleasePoolPush(); const of_unichar_t *characters = [self characters]; size_t length = [self length]; bool isStart = true; for (size_t i = 0; i < length; i++) { const of_unichar_t *const *table; size_t tableSize; of_unichar_t c = characters[i]; if (isStart) { table = startTable; tableSize = middleTableSize; } else { table = middleTable; tableSize = middleTableSize; } if (c >> 8 < tableSize && table[c >> 8][c & 0xFF]) [self setCharacter: table[c >> 8][c & 0xFF] atIndex: i]; switch (c) { case ' ': case '\t': case '\n': case '\r': isStart = true; break; default: isStart = false; break; } } objc_autoreleasePoolPop(pool); } #else - (void)OF_convertWithWordStartFunction: (int (*)(int))startFunction wordMiddleFunction: (int (*)(int))middleFunction { void *pool = objc_autoreleasePoolPush(); const of_unichar_t *characters = [self characters]; size_t length = [self length]; bool isStart = true; for (size_t i = 0; i < length; i++) { int (*function)(int) = (isStart ? startFunction : middleFunction);; of_unichar_t c = characters[i]; if (c <= 0x7F) [self setCharacter: (int)function(c) atIndex: i]; switch (c) { case ' ': case '\t': case '\n': case '\r': isStart = true; break; default: isStart = false; break; } } objc_autoreleasePoolPop(pool); } #endif - (void)setCharacter: (of_unichar_t)character atIndex: (size_t)index { void *pool = objc_autoreleasePoolPush(); OFString *string; string = [OFString stringWithCharacters: &character length: 1]; [self replaceCharactersInRange: of_range(index, 1) withString: string]; objc_autoreleasePoolPop(pool); } - (void)appendString: (OFString*)string { [self insertString: string atIndex: [self length]]; } - (void)appendCharacters: (of_unichar_t*)characters length: (size_t)length { void *pool = objc_autoreleasePoolPush(); [self appendString: [OFString stringWithCharacters: characters length: length]]; objc_autoreleasePoolPop(pool); } - (void)appendUTF8String: (const char*)UTF8String { void *pool = objc_autoreleasePoolPush(); [self appendString: [OFString stringWithUTF8String: UTF8String]]; objc_autoreleasePoolPop(pool); } - (void)appendUTF8String: (const char*)UTF8String length: (size_t)UTF8StringLength { void *pool = objc_autoreleasePoolPush(); [self appendString: [OFString stringWithUTF8String: UTF8String length: UTF8StringLength]]; objc_autoreleasePoolPop(pool); } - (void)appendCString: (const char*)cString encoding: (of_string_encoding_t)encoding { void *pool = objc_autoreleasePoolPush(); [self appendString: [OFString stringWithCString: cString encoding: encoding]]; objc_autoreleasePoolPop(pool); } - (void)appendCString: (const char*)cString encoding: (of_string_encoding_t)encoding length: (size_t)cStringLength { void *pool = objc_autoreleasePoolPush(); [self appendString: [OFString stringWithCString: cString encoding: encoding length: cStringLength]]; objc_autoreleasePoolPop(pool); } - (void)appendFormat: (OFConstantString*)format, ... { va_list arguments; va_start(arguments, format); [self appendFormat: format arguments: arguments]; va_end(arguments); } - (void)appendFormat: (OFConstantString*)format arguments: (va_list)arguments { char *UTF8String; int UTF8StringLength; if (format == nil) @throw [OFInvalidArgumentException exception]; if ((UTF8StringLength = of_vasprintf(&UTF8String, [format UTF8String], arguments)) == -1) @throw [OFInvalidFormatException exception]; @try { [self appendUTF8String: UTF8String length: UTF8StringLength]; } @finally { free(UTF8String); } } - (void)prependString: (OFString*)string { [self insertString: string atIndex: 0]; } - (void)reverse { size_t i, j, length = [self length]; for (i = 0, j = length - 1; i < length / 2; i++, j--) { of_unichar_t tmp = [self characterAtIndex: j]; [self setCharacter: [self characterAtIndex: i] atIndex: j]; [self setCharacter: tmp atIndex: i]; } } #ifdef OF_HAVE_UNICODE_TABLES - (void)uppercase { [self OF_convertWithWordStartTable: of_unicode_uppercase_table wordMiddleTable: of_unicode_uppercase_table wordStartTableSize: OF_UNICODE_UPPERCASE_TABLE_SIZE wordMiddleTableSize: OF_UNICODE_UPPERCASE_TABLE_SIZE]; } - (void)lowercase { [self OF_convertWithWordStartTable: of_unicode_lowercase_table wordMiddleTable: of_unicode_lowercase_table wordStartTableSize: OF_UNICODE_LOWERCASE_TABLE_SIZE wordMiddleTableSize: OF_UNICODE_LOWERCASE_TABLE_SIZE]; } - (void)capitalize { [self OF_convertWithWordStartTable: of_unicode_titlecase_table wordMiddleTable: of_unicode_lowercase_table wordStartTableSize: OF_UNICODE_TITLECASE_TABLE_SIZE wordMiddleTableSize: OF_UNICODE_LOWERCASE_TABLE_SIZE]; } #else - (void)uppercase { [self OF_convertWithWordStartFunction: toupperWrapper wordMiddleFunction: toupperWrapper]; } - (void)lowercase { [self OF_convertWithWordStartFunction: tolowerWrapper wordMiddleFunction: tolowerWrapper]; } - (void)capitalize { [self OF_convertWithWordStartFunction: toupperWrapper wordMiddleFunction: tolowerWrapper]; } #endif - (void)insertString: (OFString*)string atIndex: (size_t)index { [self replaceCharactersInRange: of_range(index, 0) withString: string]; } - (void)deleteCharactersInRange: (of_range_t)range { [self replaceCharactersInRange: range withString: @""]; } - (void)replaceCharactersInRange: (of_range_t)range withString: (OFString*)replacement { OF_UNRECOGNIZED_SELECTOR } - (void)replaceOccurrencesOfString: (OFString*)string withString: (OFString*)replacement { [self replaceOccurrencesOfString: string withString: replacement options: 0 range: of_range(0, [self length])]; } - (void)replaceOccurrencesOfString: (OFString*)string withString: (OFString*)replacement options: (int)options range: (of_range_t)range { void *pool = objc_autoreleasePoolPush(), *pool2; const of_unichar_t *characters; const of_unichar_t *searchCharacters = [string characters]; size_t searchLength = [string length]; size_t replacementLength = [replacement length]; if (range.length > SIZE_MAX - range.location || range.location + range.length > [self length]) @throw [OFOutOfRangeException exception]; if (searchLength > range.length) { objc_autoreleasePoolPop(pool); return; } pool2 = objc_autoreleasePoolPush(); characters = [self characters]; for (size_t i = range.location; i <= range.length - searchLength; i++) { if (memcmp(characters + i, searchCharacters, searchLength * sizeof(of_unichar_t)) != 0) continue; [self replaceCharactersInRange: of_range(i, searchLength) withString: replacement]; range.length -= searchLength; range.length += replacementLength; i += replacementLength - 1; objc_autoreleasePoolPop(pool2); pool2 = objc_autoreleasePoolPush(); characters = [self characters]; } objc_autoreleasePoolPop(pool); } - (void)deleteLeadingWhitespaces { size_t i, length = [self length]; for (i = 0; i < length; i++) { of_unichar_t c = [self characterAtIndex: i]; if (c != ' ' && c != '\t' && c != '\n' && c != '\r' && c != '\f') break; } [self deleteCharactersInRange: of_range(0, i)]; } - (void)deleteTrailingWhitespaces { size_t length = [self length]; ssize_t i; if (length - 1 > SSIZE_MAX) @throw [OFOutOfRangeException exception]; for (i = length - 1; i >= 0; i--) { of_unichar_t c = [self characterAtIndex: i]; if (c != ' ' && c != '\t' && c != '\n' && c != '\r' && c != '\f') break; } [self deleteCharactersInRange: of_range(i + 1, length - i - 1)]; } - (void)deleteEnclosingWhitespaces { [self deleteLeadingWhitespaces]; [self deleteTrailingWhitespaces]; } - copy { return [[OFString alloc] initWithString: self]; } - (void)makeImmutable { } @end