Index: src/OFConstantString.m ================================================================== --- src/OFConstantString.m +++ src/OFConstantString.m @@ -612,10 +612,17 @@ { [self finishInitialization]; return [self decomposedStringWithCanonicalMapping]; } + +- (OFString *)decomposedStringWithCompatibilityMapping +{ + [self finishInitialization]; + + return [self decomposedStringWithCompatibilityMapping]; +} #endif - (void)writeToFile: (OFString *)path { [self finishInitialization]; Index: src/OFString.h ================================================================== --- src/OFString.h +++ src/OFString.h @@ -1107,10 +1107,17 @@ * @brief Returns the string in Unicode Normalization Form D (NFD). * * @return The string in Unicode Normalization Form D (NFD) */ - (OFString *)decomposedStringWithCanonicalMapping; + +/*! + * @brief Returns the string in Unicode Normalization Form KD (NFKD). + * + * @return The string in Unicode Normalization Form KD (NFKD) + */ +- (OFString *)decomposedStringWithCompatibilityMapping; #endif #ifdef OF_HAVE_FILES /*! * @brief Writes the string into the specified file using UTF-8 encoding. Index: src/OFString.m ================================================================== --- src/OFString.m +++ src/OFString.m @@ -344,10 +344,41 @@ objc_autoreleasePoolPop(pool); return [ret autorelease]; } + +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; +} static struct { Class isa; } placeholder; @@ -2713,36 +2744,18 @@ } #ifdef OF_HAVE_UNICODE_TABLES - (OFString *)decomposedStringWithCanonicalMapping { - 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 *table; - - if (c >= OF_UNICODE_DECOMPOSITION_TABLE_SIZE) { - [ret appendCharacters: &c - length: 1]; - continue; - } - - table = of_unicode_decomposition_table[c >> 8]; - if (table != NULL && table[c & 0xFF] != NULL) - [ret appendUTF8String: table[c & 0xFF]]; - else - [ret appendCharacters: &c - length: 1]; - } - - objc_autoreleasePoolPop(pool); - - return ret; + 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_HAVE_FILES - (void)writeToFile: (OFString *)path Index: tests/OFStringTests.m ================================================================== --- tests/OFStringTests.m +++ tests/OFStringTests.m @@ -899,13 +899,19 @@ (s[0] = [mutableStringClass stringWithString: whitespace[0]]) && R([s[0] deleteEnclosingWhitespaces]) && [s[0] isEqual: @"asd"] && (s[0] = [mutableStringClass stringWithString: whitespace[1]]) && R([s[0] deleteEnclosingWhitespaces]) && [s[0] isEqual: @""]) +#ifdef OF_HAVE_UNICODE_TABLES TEST(@"-[decomposedStringWithCanonicalMapping]", - [[@"H\xC3\xA4ll\xC3\xB6" decomposedStringWithCanonicalMapping] - isEqual: @"H\x61\xCC\x88ll\x6F\xCC\x88"]); + [[@"H\xC3\xA4llj\xC3\xB6" decomposedStringWithCanonicalMapping] + isEqual: @"H\x61\xCC\x88llj\x6F\xCC\x88"]); + + TEST(@"-[decomposedStringWithCompatibilityMapping]", + [[@"H\xC3\xA4llj\xC3\xB6" decomposedStringWithCompatibilityMapping] + isEqual: @"H\x61\xCC\x88llj\x6F\xCC\x88"]); +#endif TEST(@"-[stringByXMLEscaping]", (is = [C(@" &world'\"!&") stringByXMLEscaping]) && [is isEqual: @"<hello> &world'"!&"])