/* * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im> * * All rights reserved. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3.0 only, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * version 3.0 for more details. * * You should have received a copy of the GNU Lesser General Public License * version 3.0 along with this program. If not, see * <https://www.gnu.org/licenses/>. */ #include "config.h" #include <stdlib.h> #include <string.h> #include <math.h> #import "OFStringTests.h" #ifndef INFINITY # define INFINITY __builtin_inf() #endif static const OFUnichar unicharString[] = { 0xFEFF, 'f', 0xF6, 0xF6, 'b', 0xE4, 'r', 0x1F03A, 0 }; static const OFUnichar swappedUnicharString[] = { 0xFFFE0000, 0x66000000, 0xF6000000, 0xF6000000, 0x62000000, 0xE4000000, 0x72000000, 0x3AF00100, 0 }; static const OFChar16 char16String[] = { 0xFEFF, 'f', 0xF6, 0xF6, 'b', 0xE4, 'r', 0xD83C, 0xDC3A, 0 }; static const OFChar16 swappedChar16String[] = { 0xFFFE, 0x6600, 0xF600, 0xF600, 0x6200, 0xE400, 0x7200, 0x3CD8, 0x3ADC, 0 }; static const char *range80ToFF = "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91" "\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2\xA3" "\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5" "\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7" "\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9" "\xDA\xDB\xDC\xDD\xDE\xDF\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB" "\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD" "\xFE\xFF"; @interface CustomString: OFString { OFMutableString *_string; } @end @interface CustomMutableString: OFMutableString { OFMutableString *_string; } @end @interface EntityHandler: OFObject <OFStringXMLUnescapingDelegate> @end @implementation OFStringTests - (Class)stringClass { return [CustomString class]; } - (void)setUp { [super setUp]; _string = [[self.stringClass alloc] initWithString: @"täṠ€🤔"]; } - (void)dealloc { [_string release]; [super dealloc]; } - (void)testIsEqual { OTAssertEqualObjects(_string, @"täṠ€🤔"); OTAssertEqualObjects(@"täṠ€🤔", _string); OTAssertNotEqualObjects([self.stringClass stringWithString: @"test"], @"täṠ€🤔"); OTAssertNotEqualObjects(@"täṠ€🤔", [self.stringClass stringWithString: @"test"]); } - (void)testHash { OTAssertEqual(_string.hash, @"täṠ€🤔".hash); OTAssertNotEqual([[self.stringClass stringWithString: @"test"] hash], @"täṠ€".hash); } - (void)testCopy { OTAssertEqualObjects([[_string copy] autorelease], _string); } - (void)testMutableCopy { OTAssertEqualObjects([[_string mutableCopy] autorelease], _string); } - (void)testCompare { OTAssertEqual([_string compare: @"täṠ€🤔"], OFOrderedSame); OTAssertEqual([[self.stringClass stringWithString: @""] compare: @"a"], OFOrderedAscending); OTAssertEqual([[self.stringClass stringWithString: @"a"] compare: @"b"], OFOrderedAscending); OTAssertEqual([[self.stringClass stringWithString: @"cd"] compare: @"bc"], OFOrderedDescending); OTAssertEqual([[self.stringClass stringWithString: @"ä"] compare: @"ö"], OFOrderedAscending); OTAssertEqual([[self.stringClass stringWithString: @"€"] compare: @"ß"], OFOrderedDescending); OTAssertEqual([[self.stringClass stringWithString: @"aa"] compare: @"z"], OFOrderedAscending); OTAssertEqual([@"aa" compare: [self.stringClass stringWithString: @"z"]], OFOrderedAscending); } - (void)testCaseInsensitiveCompare { #ifdef OF_HAVE_UNICODE_TABLES OTAssertEqual([[self.stringClass stringWithString: @"a"] caseInsensitiveCompare: @"A"], OFOrderedSame); OTAssertEqual([[self.stringClass stringWithString: @"Ä"] caseInsensitiveCompare: @"ä"], OFOrderedSame); OTAssertEqual([[self.stringClass stringWithString: @"я"] caseInsensitiveCompare: @"Я"], OFOrderedSame); OTAssertEqual([[self.stringClass stringWithString: @"€"] caseInsensitiveCompare: @"ß"], OFOrderedDescending); OTAssertEqual([[self.stringClass stringWithString: @"ß"] caseInsensitiveCompare: @"→"], OFOrderedAscending); OTAssertEqual([[self.stringClass stringWithString: @"AA"] caseInsensitiveCompare: @"z"], OFOrderedAscending); OTAssertEqual([[self.stringClass stringWithString: @"ABC"] caseInsensitiveCompare: @"AbD"], OFOrderedAscending); #else OTAssertEqual([[self.stringClass stringWithString: @"a"] caseInsensitiveCompare: @"A"], OFOrderedSame); OTAssertEqual([[self.stringClass stringWithString: @"AA"] caseInsensitiveCompare: @"z"], OFOrderedAscending); OTAssertEqual([[self.stringClass stringWithString: @"ABC"] caseInsensitiveCompare: @"AbD"], OFOrderedAscending); #endif } - (void)testDescription { OTAssertEqualObjects(_string.description, @"täṠ€🤔"); } - (void)testLength { OTAssertEqual(_string.length, 5); } - (void)testUTF8StringLength { OTAssertEqual(_string.UTF8StringLength, 13); } - (void)testCharacterAtIndex { OTAssertEqual([_string characterAtIndex: 0], 't'); OTAssertEqual([_string characterAtIndex: 1], 0xE4); OTAssertEqual([_string characterAtIndex: 2], 0x1E60); OTAssertEqual([_string characterAtIndex: 3], 0x20AC); OTAssertEqual([_string characterAtIndex: 4], 0x1F914); } - (void)testCharacterAtIndexFailsWithOutOfRangeIndex { OTAssertThrowsSpecific([_string characterAtIndex: 5], OFOutOfRangeException); } - (void)testUppercaseString { #ifdef OF_HAVE_UNICODE_TABLES OTAssertEqualObjects(_string.uppercaseString, @"TÄṠ€🤔"); #else OTAssertEqualObjects(_string.uppercaseString, @"TäṠ€🤔"); #endif } - (void)testLowercaseString { #ifdef OF_HAVE_UNICODE_TABLES OTAssertEqualObjects(_string.lowercaseString, @"täṡ€🤔"); #else OTAssertEqualObjects(_string.lowercaseString, @"täṠ€🤔"); #endif } - (void)testCapitalizedString { #ifdef OF_HAVE_UNICODE_TABLES OTAssertEqualObjects([[self.stringClass stringWithString: @"täṠ€🤔täṠ€🤔 täṠ€🤔"] capitalizedString], @"Täṡ€🤔täṡ€🤔 Täṡ€🤔"); #else OTAssertEqualObjects([[self.stringClass stringWithString: @"täṠ€🤔täṠ€🤔 täṠ€🤔"] capitalizedString], @"TäṠ€🤔täṠ€🤔 TäṠ€🤔"); #endif } - (void)testStringWithUTF8StringLength { OTAssertEqualObjects([self.stringClass stringWithUTF8String: "\xEF\xBB\xBF" "foobar" length: 6], @"foo"); } - (void)testStringWithUTF16String { OTAssertEqualObjects([self.stringClass stringWithUTF16String: char16String], @"fööbär🀺"); OTAssertEqualObjects([self.stringClass stringWithUTF16String: swappedChar16String], @"fööbär🀺"); } - (void)testStringWithUTF32String { OTAssertEqualObjects([self.stringClass stringWithUTF32String: unicharString], @"fööbär🀺"); OTAssertEqualObjects([self.stringClass stringWithUTF32String: swappedUnicharString], @"fööbär🀺"); } - (void)testStringWithUTF8StringFailsWithInvalidUTF8 { OTAssertThrowsSpecific( [self.stringClass stringWithUTF8String: "\xE0\x80"], OFInvalidEncodingException); OTAssertThrowsSpecific( [self.stringClass stringWithUTF8String: "\xF0\x80\x80\xC0"], OFInvalidEncodingException); } - (void)testStringWithCStringEncodingISO8859_1 { OTAssertEqualObjects([self.stringClass stringWithCString: "\xE4\xF6\xFC" encoding: OFStringEncodingISO8859_1], @"äöü"); } #ifdef HAVE_ISO_8859_15 - (void)testStringWithCStringEncodingISO8859_15 { OTAssertEqualObjects([self.stringClass stringWithCString: "a\x80\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE" encoding: OFStringEncodingISO8859_15], @"a\xC2\x80€ŠšŽžŒœŸ"); } #endif #ifdef HAVE_WINDOWS_1250 - (void)testStringWithCStringEncodingWindows1250 { OTAssertEqualObjects([self.stringClass stringWithCString: "\x80\x82\x84\x85\x86\x87\x89\x8A" "\x8B\x8C\x8D\x8E\x8F\x91\x92\x93" "\x94\x95\x96\x97\x99\x9A\x9B\x9C" "\x9D\x9E\x9F\xA0\xA1\xA2\xA3\xA4" "\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC" "\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4" "\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC" "\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4" "\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC" "\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4" "\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC" "\xDD\xDE\xDF\xE0\xE1\xE2\xE3\xE4" "\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC" "\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4" "\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC" "\xFD\xFE\xFF" encoding: OFStringEncodingWindows1250], @"€‚„…†‡‰Š‹ŚŤŽŹ‘’“”•–—™š›śťžź ˇ˘Ł¤Ą¦§¨©Ş«¬®Ż°±˛ł´µ¶·¸ąş»Ľ˝ľżŔÁÂĂÄ" @"ĹĆÇČÉĘËĚÍÎĎĐŃŇÓÔŐÖ×ŘŮÚŰÜÝŢßŕáâăäĺćçčéęëěíîďđńňóôőö÷řůúűüýţ˙"); } #endif #ifdef HAVE_WINDOWS_1252 - (void)testStringWithCStringEncodingWindows1252 { OTAssertEqualObjects([self.stringClass stringWithCString: "\x80\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B" "\x8C\x8E\x91\x92\x93\x94\x95\x96\x97\x98\x99" "\x9A\x9B\x9C\x9E\x9F" encoding: OFStringEncodingWindows1252], @"€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ"); } #endif #ifdef HAVE_CODEPAGE_437 - (void)testStringWithCStringEncodingCodepage437 { OTAssertEqualObjects([self.stringClass stringWithCString: range80ToFF encoding: OFStringEncodingCodepage437], @"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛" @"┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²" @"■ "); } #endif #ifdef HAVE_CODEPAGE_850 - (void)testStringWithCStringEncodingCodepage850 { OTAssertEqualObjects([self.stringClass stringWithCString: range80ToFF encoding: OFStringEncodingCodepage850], @"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«»░▒▓│┤ÁÂÀ©╣║╗╝¢¥" @"┐└┴┬├─┼ãÃ╚╔╩╦╠═╬¤ðÐÊËÈıÍÎÏ┘┌█▄¦Ì▀ÓßÔÒõÕµþÞÚÛÙýݯ´±‗¾¶§÷¸°¨·¹³²" @"■ "); } #endif #ifdef HAVE_CODEPAGE_852 - (void)testStringWithCStringEncodingCodepage852 { OTAssertEqualObjects([self.stringClass stringWithCString: range80ToFF encoding: OFStringEncodingCodepage852], @"ÇüéâäůćçłëŐőîŹÄĆÉĹĺôöĽľŚśÖÜŤťŁ×čáíóúĄąŽžĘ꬟Ⱥ«»░▒▓│┤ÁÂĚŞ╣║╗╝Żż" @"┐└┴┬├─┼Ăă╚╔╩╦╠═╬¤đĐĎËďŇÍÎě┘┌█▄ŢŮ▀ÓßÔŃńňŠšŔÚŕŰýÝţ´˝˛ˇ˘§÷¸°¨˙űŘř" @"■ "); } #endif #ifdef HAVE_CODEPAGE_858 - (void)testStringWithCStringEncodingCodepage858 { OTAssertEqualObjects([self.stringClass stringWithCString: range80ToFF encoding: OFStringEncodingCodepage858], @"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«»░▒▓│┤ÁÂÀ©╣║╗╝¢¥" @"┐└┴┬├─┼ãÃ╚╔╩╦╠═╬¤ðÐÊËÈ€ÍÎÏ┘┌█▄¦Ì▀ÓßÔÒõÕµþÞÚÛÙýݯ´±‗¾¶§÷¸°¨·¹³²" @"■ "); } #endif #ifdef OF_HAVE_FILES - (void)testStringWithContentsOfFileEncoding { OTAssertEqualObjects([self.stringClass stringWithContentsOfFile: @"testfile.txt" encoding: OFStringEncodingISO8859_1], @"testäöü"); } - (void)testStringWithContentsOfIRIEncoding { OTAssertEqualObjects([self.stringClass stringWithContentsOfIRI: [OFIRI fileIRIWithPath: @"testfile.txt"] encoding: OFStringEncodingISO8859_1], @"testäöü"); } #endif - (void)testCStringWithEncodingASCII { OTAssertEqual(strcmp([[self.stringClass stringWithString: @"This is a test"] cStringWithEncoding: OFStringEncodingASCII], "This is a test"), 0); OTAssertThrowsSpecific([[self.stringClass stringWithString: @"This is a tést"] cStringWithEncoding: OFStringEncodingASCII], OFInvalidEncodingException); } - (void)testCStringWithEncodingISO8859_1 { OTAssertEqual(strcmp([[self.stringClass stringWithString: @"This is ä test"] cStringWithEncoding: OFStringEncodingISO8859_1], "This is \xE4 test"), 0); OTAssertThrowsSpecific([[self.stringClass stringWithString: @"This is ä t€st"] cStringWithEncoding: OFStringEncodingISO8859_1], OFInvalidEncodingException); } #ifdef HAVE_ISO_8859_15 - (void)testCStringWithEncodingISO8859_15 { OTAssertEqual(strcmp([[self.stringClass stringWithString: @"This is ä t€st"] cStringWithEncoding: OFStringEncodingISO8859_15], "This is \xE4 t\xA4st"), 0); OTAssertThrowsSpecific( [[self.stringClass stringWithString: @"This is ä t€st…"] cStringWithEncoding: OFStringEncodingISO8859_15], OFInvalidEncodingException); } #endif #ifdef HAVE_WINDOWS_1250 - (void)testCStringWithEncodingWindows1250 { OTAssertEqual( strcmp([[self.stringClass stringWithString: @"€‚„…†‡‰Š‹ŚŤŽŹ‘’“”•–—™š›śťžź ˇ˘Ł¤Ą¦§¨©Ş«¬®Ż°±˛ł´µ¶·¸ąş»Ľ˝ľżŔÁÂĂÄ" @"ĹĆÇČÉĘËĚÍÎĎĐŃŇÓÔŐÖ×ŘŮÚŰÜÝŢßŕáâăäĺćçčéęëěíîďđńňóôőö÷řůúűüýţ˙"] cStringWithEncoding: OFStringEncodingWindows1250], "\x80\x82\x84\x85\x86\x87\x89\x8A\x8B\x8C\x8D\x8E\x8F\x91\x92\x93" "\x94\x95\x96\x97\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2\xA3\xA4" "\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4" "\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4" "\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4" "\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xE0\xE1\xE2\xE3\xE4" "\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4" "\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF"), 0); OTAssertThrowsSpecific( [[self.stringClass stringWithString: @"This is ä t€st…‼"] cStringWithEncoding: OFStringEncodingWindows1250], OFInvalidEncodingException); } #endif #ifdef HAVE_WINDOWS_1252 - (void)testCStringWithEncodingWindows1252 { OTAssertEqual( strcmp([[self.stringClass stringWithString: @"This is ä t€st…"] cStringWithEncoding: OFStringEncodingWindows1252], "This is \xE4 t\x80st\x85"), 0); OTAssertThrowsSpecific( [[self.stringClass stringWithString: @"This is ä t€st…‼"] cStringWithEncoding: OFStringEncodingWindows1252], OFInvalidEncodingException); } #endif #ifdef HAVE_CODEPAGE_437 - (void)testCStringWithEncodingCodepage437 { OTAssertEqual( strcmp([[self.stringClass stringWithString: @"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛" @"┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²" @"■ "] cStringWithEncoding: OFStringEncodingCodepage437], range80ToFF), 0); OTAssertThrowsSpecific( [[self.stringClass stringWithString: @"T€st strîng ░▒▓"] cStringWithEncoding: OFStringEncodingCodepage437], OFInvalidEncodingException); } #endif #ifdef HAVE_CODEPAGE_850 - (void)testCStringWithEncodingCodepage850 { OTAssertEqual( strcmp([[self.stringClass stringWithString: @"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«»░▒▓│┤ÁÂÀ©╣║╗╝¢¥" @"┐└┴┬├─┼ãÃ╚╔╩╦╠═╬¤ðÐÊËÈıÍÎÏ┘┌█▄¦Ì▀ÓßÔÒõÕµþÞÚÛÙýݯ´±‗¾¶§÷¸°¨·¹³²" @"■ "] cStringWithEncoding: OFStringEncodingCodepage850], range80ToFF), 0); } #endif #ifdef HAVE_CODEPAGE_852 - (void)testCStringWithEncodingCodepage852 { OTAssertEqual( strcmp([[self.stringClass stringWithString: @"ÇüéâäůćçłëŐőîŹÄĆÉĹĺôöĽľŚśÖÜŤťŁ×čáíóúĄąŽžĘ꬟Ⱥ«»░▒▓│┤ÁÂĚŞ╣║╗╝Żż" @"┐└┴┬├─┼Ăă╚╔╩╦╠═╬¤đĐĎËďŇÍÎě┘┌█▄ŢŮ▀ÓßÔŃńňŠšŔÚŕŰýÝţ´˝˛ˇ˘§÷¸°¨˙űŘř" @"■ "] cStringWithEncoding: OFStringEncodingCodepage852], range80ToFF), 0); } #endif #ifdef HAVE_CODEPAGE_858 - (void)testCStringWithEncodingCodepage858 { OTAssertEqual( strcmp([[self.stringClass stringWithString: @"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«»░▒▓│┤ÁÂÀ©╣║╗╝¢¥" @"┐└┴┬├─┼ãÃ╚╔╩╦╠═╬¤ðÐÊËÈ€ÍÎÏ┘┌█▄¦Ì▀ÓßÔÒõÕµþÞÚÛÙýݯ´±‗¾¶§÷¸°¨·¹³²" @"■ "] cStringWithEncoding: OFStringEncodingCodepage858], range80ToFF), 0); } #endif - (void)testLossyCStringWithEncodingASCII { OTAssertEqual(strcmp([[self.stringClass stringWithString: @"This is a tést"] lossyCStringWithEncoding: OFStringEncodingASCII], "This is a t?st"), 0); } - (void)testLossyCStringWithEncodingISO8859_1 { OTAssertEqual( strcmp([[self.stringClass stringWithString: @"This is ä t€st"] lossyCStringWithEncoding: OFStringEncodingISO8859_1], "This is \xE4 t?st"), 0); } #ifdef HAVE_ISO_8859_15 - (void)testLossyCStringWithEncodingISO8859_15 { OTAssertEqual( strcmp([[self.stringClass stringWithString: @"This is ä t€st…"] lossyCStringWithEncoding: OFStringEncodingISO8859_15], "This is \xE4 t\xA4st?"), 0); } #endif #ifdef HAVE_WINDOWS_1250 - (void)testLossyCStringWithEncodingWindows1250 { OTAssertEqual( strcmp([[self.stringClass stringWithString: @"This is ä t€st…‼"] lossyCStringWithEncoding: OFStringEncodingWindows1250], "This is \xE4 t\x80st\x85?"), 0); } #endif #ifdef HAVE_WINDOWS_1252 - (void)testLossyCStringWithEncodingWindows1252 { OTAssertEqual( strcmp([[self.stringClass stringWithString: @"This is ä t€st…‼"] lossyCStringWithEncoding: OFStringEncodingWindows1252], "This is \xE4 t\x80st\x85?"), 0); } #endif #ifdef HAVE_CODEPAGE_437 - (void)testLossyCStringWithEncodingCodepage437 { OTAssertEqual( strcmp([[self.stringClass stringWithString: @"T€st strîng ░▒▓"] lossyCStringWithEncoding: OFStringEncodingCodepage437], "T?st str\x8Cng \xB0\xB1\xB2"), 0); } #endif - (void)testStringWithFormat { OTAssertEqualObjects( ([self.stringClass stringWithFormat: @"%@:%d", @"test", 123]), @"test:123"); } - (void)testRangeOfString { OFString *string = [self.stringClass stringWithString: @"𝄞öö"]; OTAssertEqual([string rangeOfString: @"öö"].location, 1); OTAssertEqual([string rangeOfString: @"ö"].location, 1); OTAssertEqual([string rangeOfString: @"𝄞"].location, 0); OTAssertEqual([string rangeOfString: @"x"].location, OFNotFound); OTAssertEqual([string rangeOfString: @"öö" options: OFStringSearchBackwards].location, 1); OTAssertEqual([string rangeOfString: @"ö" options: OFStringSearchBackwards].location, 2); OTAssertEqual([string rangeOfString: @"𝄞" options: OFStringSearchBackwards].location, 0); OTAssertEqual([string rangeOfString: @"x" options: OFStringSearchBackwards].location, OFNotFound); } - (void)testRangeOfStringFailsWithOutOfRangeRange { OTAssertThrowsSpecific( [_string rangeOfString: @"t" options: 0 range: OFMakeRange(6, 1)], OFOutOfRangeException); } - (void)testIndexOfCharacterFromSet { OFCharacterSet *characterSet = [OFCharacterSet characterSetWithCharactersInString: @"cđ"]; OTAssertEqual([[self.stringClass stringWithString: @"abcđabcđe"] indexOfCharacterFromSet: characterSet], 2); OTAssertEqual([[self.stringClass stringWithString: @"abcđabcđë"] indexOfCharacterFromSet: characterSet options: OFStringSearchBackwards], 7); OTAssertEqual([[self.stringClass stringWithString: @"abcđabcđë"] indexOfCharacterFromSet: characterSet options: 0 range: OFMakeRange(4, 4)], 6); OTAssertEqual([[self.stringClass stringWithString: @"abcđabcđëf"] indexOfCharacterFromSet: characterSet options: 0 range: OFMakeRange(8, 2)], OFNotFound); } - (void)testIndexOfCharacterFromSetFailsWithOutOfRangeRange { OFCharacterSet *characterSet = [OFCharacterSet characterSetWithCharactersInString: @"cđ"]; OTAssertThrowsSpecific([[self.stringClass stringWithString: @"𝄞öö"] indexOfCharacterFromSet: characterSet options: 0 range: OFMakeRange(3, 1)], OFOutOfRangeException); } - (void)testSubstringWithRange { OTAssertEqualObjects([_string substringWithRange: OFMakeRange(1, 2)], @"äṠ"); OTAssertEqualObjects([_string substringWithRange: OFMakeRange(3, 0)], @""); } - (void)testSubstringWithRangeFailsWithOutOfRangeRange { OTAssertThrowsSpecific([_string substringWithRange: OFMakeRange(4, 2)], OFOutOfRangeException); OTAssertThrowsSpecific([_string substringWithRange: OFMakeRange(6, 0)], OFOutOfRangeException); } - (void)testStringByAppendingString { OTAssertEqualObjects([_string stringByAppendingString: @"äöü"], @"täṠ€🤔äöü"); } - (void)testHasPrefix { OTAssertTrue([_string hasPrefix: @"täṠ"]); OTAssertFalse([_string hasPrefix: @"🤔"]); } - (void)testHasSuffix { OTAssertTrue([_string hasSuffix: @"🤔"]); OTAssertFalse([_string hasSuffix: @"täṠ"]); } - (void)testComponentsSeparatedByString { OTAssertEqualObjects([[self.stringClass stringWithString: @"fooXXbarXXXXbazXXXX"] componentsSeparatedByString: @"XX"], ([OFArray arrayWithObjects: @"foo", @"bar", @"", @"baz", @"", @"", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo"] componentsSeparatedByString: @""], [OFArray arrayWithObject: @"foo"]); } - (void)testComponentsSeparatedByStringOptions { OTAssertEqualObjects([[self.stringClass stringWithString: @"fooXXbarXXXXbazXXXX"] componentsSeparatedByString: @"XX" options: OFStringSkipEmptyComponents], ([OFArray arrayWithObjects: @"foo", @"bar", @"baz", nil])); } - (void)testComponentsSeparatedByCharactersInSet { OFCharacterSet *characterSet = [OFCharacterSet characterSetWithCharactersInString: @"XYZ"]; OTAssertEqualObjects([[self.stringClass stringWithString: @"fooXYbarXYZXbazXYXZx"] componentsSeparatedByCharactersInSet: characterSet], ([OFArray arrayWithObjects: @"foo", @"", @"bar", @"", @"", @"", @"baz", @"", @"", @"", @"x", nil])); } - (void)testComponentsSeparatedByCharactersInSetOptions { OFCharacterSet *characterSet = [OFCharacterSet characterSetWithCharactersInString: @"XYZ"]; OTAssertEqualObjects([[self.stringClass stringWithString: @"fooXYbarXYZXbazXYXZ"] componentsSeparatedByCharactersInSet: characterSet options: OFStringSkipEmptyComponents], ([OFArray arrayWithObjects: @"foo", @"bar", @"baz", nil])); } - (void)testLongLongValue { OTAssertEqual([[self.stringClass stringWithString: @"1234"] longLongValue], 1234); OTAssertEqual([[self.stringClass stringWithString: @"\r\n+123 "] longLongValue], 123); OTAssertEqual([[self.stringClass stringWithString: @"-500\t"] longLongValue], -500); OTAssertEqual([[self.stringClass stringWithString: @"-0x10\t"] longLongValueWithBase: 0], -0x10); OTAssertEqual([[self.stringClass stringWithString: @"\t\t\r\n"] longLongValue], 0); OTAssertEqual([[self.stringClass stringWithString: @"123f"] longLongValueWithBase: 16], 0x123f); OTAssertEqual([[self.stringClass stringWithString: @"-1234"] longLongValueWithBase: 0], -1234); OTAssertEqual([[self.stringClass stringWithString: @"\t\n0xABcd\r"] longLongValueWithBase: 0], 0xABCD); OTAssertEqual([[self.stringClass stringWithString: @"1234567"] longLongValueWithBase: 8], 01234567); OTAssertEqual([[self.stringClass stringWithString: @"\r\n0123"] longLongValueWithBase: 0], 0123); OTAssertEqual([[self.stringClass stringWithString: @"765\t"] longLongValueWithBase: 8], 0765); OTAssertEqual([[self.stringClass stringWithString: @"\t\t\r\n"] longLongValueWithBase: 8], 0); } - (void)testLongLongValueThrowsOnInvalidFormat { OTAssertThrowsSpecific( [[self.stringClass stringWithString: @"abc"] longLongValue], OFInvalidFormatException); OTAssertThrowsSpecific( [[self.stringClass stringWithString: @"0a"] longLongValue], OFInvalidFormatException); OTAssertThrowsSpecific( [[self.stringClass stringWithString: @"0 1"] longLongValue], OFInvalidFormatException); OTAssertThrowsSpecific( [[self.stringClass stringWithString: @"0xABCDEFG"] longLongValueWithBase: 0], OFInvalidFormatException); OTAssertThrowsSpecific( [[self.stringClass stringWithString: @"0x"] longLongValueWithBase: 0], OFInvalidFormatException); } - (void)testLongLongValueThrowsOnOutOfRange { OTAssertThrowsSpecific([[self.stringClass stringWithString: @"-12345678901234567890123456789012345678901234567890" @"12345678901234567890123456789012345678901234567890"] longLongValueWithBase: 16], OFOutOfRangeException) } - (void)testUnsignedLongLongValue { OTAssertEqual([[self.stringClass stringWithString: @"1234"] unsignedLongLongValue], 1234); OTAssertEqual([[self.stringClass stringWithString: @"\r\n+123 "] unsignedLongLongValue], 123); OTAssertEqual([[self.stringClass stringWithString: @"\t\t\r\n"] unsignedLongLongValue], 0); OTAssertEqual([[self.stringClass stringWithString: @"123f"] unsignedLongLongValueWithBase: 16], 0x123f); OTAssertEqual([[self.stringClass stringWithString: @"1234"] unsignedLongLongValueWithBase: 0], 1234); OTAssertEqual([[self.stringClass stringWithString: @"\t\n0xABcd\r"] unsignedLongLongValueWithBase: 0], 0xABCD); OTAssertEqual([[self.stringClass stringWithString: @"1234567"] unsignedLongLongValueWithBase: 8], 01234567); OTAssertEqual([[self.stringClass stringWithString: @"\r\n0123"] unsignedLongLongValueWithBase: 0], 0123); OTAssertEqual([[self.stringClass stringWithString: @"765\t"] unsignedLongLongValueWithBase: 8], 0765); OTAssertEqual([[self.stringClass stringWithString: @"\t\t\r\n"] unsignedLongLongValueWithBase: 8], 0); } - (void)testUnsignedLongLongValueThrowsOnOutOfRange { OTAssertThrowsSpecific([[self.stringClass stringWithString: @"0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" @"0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"] unsignedLongLongValueWithBase: 16], OFOutOfRangeException); } - (void)testFloatValue { /* * These test numbers can be generated without rounding if we have IEEE * floating point numbers, thus we can use OTAssertEqual on them. */ OTAssertEqual([[self.stringClass stringWithString: @"\t-0.25 "] floatValue], -0.25); OTAssertEqual([[self.stringClass stringWithString: @"\r\n\tINF\t\n"] floatValue], INFINITY); OTAssertEqual([[self.stringClass stringWithString: @"\r -INFINITY\n"] floatValue], -INFINITY); OTAssertTrue(isnan([[self.stringClass stringWithString: @" NAN\t\t"] floatValue])); OTAssertTrue(isnan([[self.stringClass stringWithString: @" -NaN\t\t"] floatValue])); } - (void)testFloatValueThrowsOnInvalidFormat { OTAssertThrowsSpecific([[self.stringClass stringWithString: @"0.0a"] floatValue], OFInvalidFormatException); OTAssertThrowsSpecific([[self.stringClass stringWithString: @"0 0"] floatValue], OFInvalidFormatException); OTAssertThrowsSpecific([[self.stringClass stringWithString: @"0,0"] floatValue], OFInvalidFormatException); } - (void)testDoubleValue { #if (defined(OF_SOLARIS) && defined(OF_X86)) || defined(OF_AMIGAOS_M68K) /* * Solaris' strtod() has weird rounding on x86, but not on AMD64. * AmigaOS 3 with libnix has weird rounding as well. */ OTAssertEqual([[self.stringClass stringWithString: @"\t-0.125 "] doubleValue], -0.125); #elif defined(OF_ANDROID) || defined(OF_SOLARIS) || defined(OF_HPUX) || \ defined(OF_DJGPP) || defined(OF_AMIGAOS_M68K) /* * Android, Solaris, HP-UX, DJGPP and AmigaOS 3 do not accept 0x for * strtod(). */ OTAssertEqual([[self.stringClass stringWithString: @"\t-0.123456789 "] doubleValue], -0.123456789); #else OTAssertEqual([[self.stringClass stringWithString: @"\t-0x1.FFFFFFFFFFFFFP-1020 "] doubleValue], -0x1.FFFFFFFFFFFFFP-1020); #endif OTAssertEqual([[self.stringClass stringWithString: @"\r\n\tINF\t\n"] doubleValue], INFINITY); OTAssertEqual([[self.stringClass stringWithString: @"\r -INFINITY\n"] doubleValue], -INFINITY); OTAssert(isnan([[self.stringClass stringWithString: @" NAN\t\t"] doubleValue])); OTAssert(isnan([[self.stringClass stringWithString: @" -NaN\t\t"] doubleValue])); } - (void)testDoubleValueThrowsOnInvalidFormat { OTAssertThrowsSpecific([[self.stringClass stringWithString: @"0.0a"] doubleValue], OFInvalidFormatException); OTAssertThrowsSpecific([[self.stringClass stringWithString: @"0 0"] doubleValue], OFInvalidFormatException); OTAssertThrowsSpecific([[self.stringClass stringWithString: @"0,0"] doubleValue], OFInvalidFormatException); } - (void)testCharacters { OTAssertEqual(memcmp([[self.stringClass stringWithString: @"fööbär🀺"] characters], unicharString + 1, sizeof(unicharString) - 8), 0); } - (void)testUTF16String { OFString *string = [self.stringClass stringWithString: @"fööbär🀺"]; OTAssertEqual(memcmp(string.UTF16String, char16String + 1, OFUTF16StringLength(char16String) * 2), 0); #ifdef OF_BIG_ENDIAN OTAssertEqual(memcmp([string UTF16StringWithByteOrder: OFByteOrderLittleEndian], swappedChar16String + 1, OFUTF16StringLength(swappedChar16String) * 2), 0); #else OTAssertEqual(memcmp([string UTF16StringWithByteOrder: OFByteOrderBigEndian], swappedChar16String + 1, OFUTF16StringLength(swappedChar16String) * 2), 0); #endif } - (void)testUTF16StringLength { OTAssertEqual(_string.UTF16StringLength, 6); } - (void)testUTF32String { OFString *string = [self.stringClass stringWithString: @"fööbär🀺"]; OTAssertEqual(memcmp(string.UTF32String, unicharString + 1, OFUTF32StringLength(unicharString) * 4), 0); #ifdef OF_BIG_ENDIAN OTAssertEqual(memcmp([string UTF32StringWithByteOrder: OFByteOrderLittleEndian], swappedUnicharString + 1, OFUTF32StringLength(swappedUnicharString) * 4), 0); #else OTAssertEqual(memcmp([string UTF32StringWithByteOrder: OFByteOrderBigEndian], swappedUnicharString + 1, OFUTF32StringLength(swappedUnicharString) * 4), 0); #endif } - (void)testStringByMD5Hashing { OTAssertEqualObjects(_string.stringByMD5Hashing, @"7e6bef5fe100d93e808d15b1c6e6145a"); } - (void)testStringByRIPEMD160Hashing { OTAssertEqualObjects(_string.stringByRIPEMD160Hashing, @"2fd0ec899c55cf2821a2f844b9d80887fc351103"); } - (void)testStringBySHA1Hashing { OTAssertEqualObjects(_string.stringBySHA1Hashing, @"3f76f9358b372b7147344b7a3ba6d309e4466b3a"); } - (void)testStringBySHA224Hashing { OTAssertEqualObjects(_string.stringBySHA224Hashing, @"6e57ec72e4da55c46d88a15ce7ce4d8db83d0493a263134a3734259d"); } - (void)testStringBySHA256Hashing { OTAssertEqualObjects(_string.stringBySHA256Hashing, @"6eac4d3d0b4152c82ff88599482696ca" @"d6dca0b533e8a2e6963d995b19b0a683"); } - (void)testStringBySHA384Hashing { OTAssertEqualObjects(_string.stringBySHA384Hashing, @"d9bd6a671407d01cee4022888677040d" @"108dd0270c38e0ce755d6dcadb4bf9c1" @"89204dd2a51f954be55ea5d5fe00667b"); } - (void)testStringBySHA512Hashing { OTAssertEqualObjects(_string.stringBySHA512Hashing, @"64bec66b3633c585da6d32760fa3617a" @"47ca4c247472bdbbfb452b2dbf5a3612" @"5629053394a16ecd08f8a21d461537c5" @"f1224cbb379589e73dcd6763ec4f886c"); } - (void)testStringByAddingPercentEncodingWithAllowedCharacters { OFCharacterSet *characterSet = [OFCharacterSet characterSetWithCharactersInString: @"abfo'_~$🍏"]; OTAssertEqualObjects([[self.stringClass stringWithString: @"foo\"ba'_~$]🍏🍌"] stringByAddingPercentEncodingWithAllowedCharacters: characterSet], @"foo%22ba'_~$%5D🍏%F0%9F%8D%8C"); } - (void)testStringByRemovingPercentEncoding { OTAssertEqualObjects([[self.stringClass stringWithString: @"foo%20bar%22+%24%F0%9F%8D%8C"] stringByRemovingPercentEncoding], @"foo bar\"+$🍌"); } - (void)testStringByRemovingPercentEncodingThrowsOnInvalidFormat { OTAssertThrowsSpecific([[self.stringClass stringWithString: @"foo%xbar"] stringByRemovingPercentEncoding], OFInvalidFormatException); } - (void)testStringByRemovingPercentEncodingThrowsOnInvalidEncoding { OTAssertThrowsSpecific([[self.stringClass stringWithString: @"foo%FFbar"] stringByRemovingPercentEncoding], OFInvalidEncodingException); } - (void)testStringByXMLEscaping { OTAssertEqualObjects([[self.stringClass stringWithString: @"<hello> &world'\"!&"] stringByXMLEscaping], @"<hello> &world'"!&"); } - (void)testStringByXMLUnescaping { OTAssertEqualObjects([[self.stringClass stringWithString: @"<hello> &world'"!&"] stringByXMLUnescaping], @"<hello> &world'\"!&"); OTAssertEqualObjects([[self.stringClass stringWithString: @"y"] stringByXMLUnescaping], @"y"); OTAssertEqualObjects([[self.stringClass stringWithString: @"ä"] stringByXMLUnescaping], @"ä"); OTAssertEqualObjects([[self.stringClass stringWithString: @"€"] stringByXMLUnescaping], @"€"); OTAssertEqualObjects([[self.stringClass stringWithString: @"𝄞"] stringByXMLUnescaping], @"𝄞"); } - (void)testStringByXMLUnescapingThrowsOnUnknownEntities { OTAssertThrowsSpecific([[self.stringClass stringWithString: @"&foo;"] stringByXMLUnescaping], OFUnknownXMLEntityException); } - (void)testStringByXMLUnescapingThrowsOnInvalidFormat { OTAssertThrowsSpecific([[self.stringClass stringWithString: @"x&"] stringByXMLUnescaping], OFInvalidFormatException); OTAssertThrowsSpecific([[self.stringClass stringWithString: @"&#;"] stringByXMLUnescaping], OFInvalidFormatException); OTAssertThrowsSpecific([[self.stringClass stringWithString: @"&#x;"] stringByXMLUnescaping], OFInvalidFormatException); OTAssertThrowsSpecific([[self.stringClass stringWithString: @"&#g;"] stringByXMLUnescaping], OFInvalidFormatException); OTAssertThrowsSpecific([[self.stringClass stringWithString: @"&#xg;"] stringByXMLUnescaping], OFInvalidFormatException); } - (void)testStringByXMLUnescapingWithDelegate { EntityHandler *entityHandler = [[[EntityHandler alloc] init] autorelease]; OTAssertEqualObjects([[self.stringClass stringWithString: @"x&foo;y"] stringByXMLUnescapingWithDelegate: entityHandler], @"xbary"); } #ifdef OF_HAVE_BLOCKS - (void)testStringByXMLUnescapingWithBlock { OTAssertEqualObjects([[self.stringClass stringWithString: @"x&foo;y"] stringByXMLUnescapingWithBlock: ^ OFString * (OFString *string, OFString *entity) { if ([entity isEqual: @"foo"]) return @"bar"; return nil; }], @"xbary"); } - (void)testEnumerateLinesUsingBlock { __block size_t count = 0; [[self.stringClass stringWithString: @"foo\nbar\nbaz"] enumerateLinesUsingBlock: ^ (OFString *line, bool *stop) { switch (count++) { case 0: OTAssertEqualObjects(line, @"foo"); break; case 1: OTAssertEqualObjects(line, @"bar"); break; case 2: OTAssertEqualObjects(line, @"baz"); break; default: OTAssert(false); } }]; OTAssertEqual(count, 3); } #endif #ifdef OF_HAVE_FILES - (void)testIsAbsolutePath { # if defined(OF_WINDOWS) || defined(OF_MSDOS) OTAssertTrue( [[self.stringClass stringWithString: @"C:\\foo"] isAbsolutePath]); OTAssertTrue( [[self.stringClass stringWithString: @"a:/foo"] isAbsolutePath]); OTAssertFalse( [[self.stringClass stringWithString: @"foo"] isAbsolutePath]); OTAssertFalse( [[self.stringClass stringWithString: @"b:foo"] isAbsolutePath]); # ifdef OF_WINDOWS OTAssertTrue( [[self.stringClass stringWithString: @"\\\\foo"] isAbsolutePath]); # endif # elif defined(OF_AMIGAOS) OTAssertTrue( [[self.stringClass stringWithString: @"dh0:foo"] isAbsolutePath]); OTAssertTrue( [[self.stringClass stringWithString: @"dh0:a/b"] isAbsolutePath]); OTAssertFalse( [[self.stringClass stringWithString: @"foo/bar"] isAbsolutePath]); OTAssertFalse( [[self.stringClass stringWithString: @"foo"] isAbsolutePath]); # elif defined(OF_NINTENDO_DS) || defined(OF_NINTENDO_3DS) || \ defined(OF_WII) || defined(OF_NINTENDO_SWITCH) OTAssertTrue( [[self.stringClass stringWithString: @"sdmc:/foo"] isAbsolutePath]); OTAssertFalse( [[self.stringClass stringWithString: @"sdmc:foo"] isAbsolutePath]); OTAssertFalse( [[self.stringClass stringWithString: @"foo/bar"] isAbsolutePath]); OTAssertFalse( [[self.stringClass stringWithString: @"foo"] isAbsolutePath]); # else OTAssertTrue( [[self.stringClass stringWithString: @"/foo"] isAbsolutePath]); OTAssertTrue( [[self.stringClass stringWithString: @"/foo/bar"] isAbsolutePath]); OTAssertFalse( [[self.stringClass stringWithString: @"foo/bar"] isAbsolutePath]); OTAssertFalse( [[self.stringClass stringWithString: @"foo"] isAbsolutePath]); # endif } - (void)testStringByAppendingPathComponent { # if defined(OF_WINDOWS) || defined(OF_MSDOS) OTAssertEqualObjects([[self.stringClass stringWithString: @"foo\\bar"] stringByAppendingPathComponent: @"baz"], @"foo\\bar\\baz"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo\\bar\\"] stringByAppendingPathComponent: @"baz"], @"foo\\bar\\baz"); # else OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar"] stringByAppendingPathComponent: @"baz"], @"foo/bar/baz"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar/"] stringByAppendingPathComponent: @"baz"], @"foo/bar/baz"); # endif } - (void)testStringByAppendingPathExtension { # if defined(OF_WINDOWS) || defined(OF_MSDOS) OTAssertEqualObjects([[self.stringClass stringWithString: @"foo"] stringByAppendingPathExtension: @"bar"], @"foo.bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"c:\\tmp\\foo"] stringByAppendingPathExtension: @"bar"], @"c:\\tmp\\foo.bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"c:\\tmp\\/\\"] stringByAppendingPathExtension: @"bar"], @"c:\\tmp.bar"); # elif defined(OF_AMIGAOS) OTAssertEqualObjects([[self.stringClass stringWithString: @"foo"] stringByAppendingPathExtension: @"bar"], @"foo.bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar"] stringByAppendingPathExtension: @"baz"], @"foo/bar.baz"); # else OTAssertEqualObjects([[self.stringClass stringWithString: @"foo"] stringByAppendingPathExtension: @"bar"], @"foo.bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar"] stringByAppendingPathExtension: @"baz"], @"foo/bar.baz"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo///"] stringByAppendingPathExtension: @"bar"], @"foo.bar"); # endif } - (void)testPathWithComponents { # if defined(OF_WINDOWS) || defined(OF_MSDOS) OTAssertEqualObjects([self.stringClass pathWithComponents: ([OFArray arrayWithObjects: @"foo", @"bar", @"baz", nil])], @"foo\\bar\\baz"); OTAssertEqualObjects([self.stringClass pathWithComponents: ([OFArray arrayWithObjects: @"c:\\", @"foo", @"bar", @"baz", nil])], @"c:\\foo\\bar\\baz"); OTAssertEqualObjects([self.stringClass pathWithComponents: ([OFArray arrayWithObjects: @"c:", @"foo", @"bar", @"baz", nil])], @"c:foo\\bar\\baz"); OTAssertEqualObjects([self.stringClass pathWithComponents: ([OFArray arrayWithObjects: @"c:", @"\\", @"foo", @"bar", @"baz", nil])], @"c:\\foo\\bar\\baz"); OTAssertEqualObjects([self.stringClass pathWithComponents: ([OFArray arrayWithObjects: @"c:", @"/", @"foo", @"bar", @"baz", nil])], @"c:/foo\\bar\\baz"); OTAssertEqualObjects([self.stringClass pathWithComponents: ([OFArray arrayWithObjects: @"foo/", @"bar\\", @"", @"baz", @"\\", nil])], @"foo/bar\\baz"); OTAssertEqualObjects([self.stringClass pathWithComponents: [OFArray arrayWithObject: @"foo"]], @"foo"); OTAssertEqualObjects([self.stringClass pathWithComponents: [OFArray arrayWithObject: @"c:"]], @"c:"); OTAssertEqualObjects([self.stringClass pathWithComponents: [OFArray arrayWithObject: @"c:\\"]], @"c:\\"); OTAssertEqualObjects([self.stringClass pathWithComponents: [OFArray arrayWithObject: @"\\"]], @"\\"); OTAssertEqualObjects([self.stringClass pathWithComponents: [OFArray arrayWithObject: @"/"]], @"/"); # ifdef OF_WINDOWS OTAssertEqualObjects([self.stringClass pathWithComponents: ([OFArray arrayWithObjects: @"\\\\", @"foo", @"bar", nil])], @"\\\\foo\\bar"); # endif # elif defined(OF_AMIGAOS) OTAssertEqualObjects([self.stringClass pathWithComponents: ([OFArray arrayWithObjects: @"dh0:", @"foo", @"bar", @"baz", nil])], @"dh0:foo/bar/baz"); OTAssertEqualObjects([self.stringClass pathWithComponents: ([OFArray arrayWithObjects: @"foo", @"bar", @"baz", nil])], @"foo/bar/baz"); OTAssertEqualObjects([self.stringClass pathWithComponents: ([OFArray arrayWithObjects: @"foo/", @"bar", @"", @"baz", @"/", nil])], @"foo//bar/baz//"); OTAssertEqualObjects([self.stringClass pathWithComponents: [OFArray arrayWithObject: @"foo"]], @"foo"); # elif defined(OF_WII) || defined(OF_NINTENDO_DS) || \ defined(OF_NINTENDO_3DS) || defined(OF_NINTENDO_SWITCH) OTAssertEqualObjects([self.stringClass pathWithComponents: ([OFArray arrayWithObjects: @"foo", @"bar", @"baz", nil])], @"foo/bar/baz"); OTAssertEqualObjects([self.stringClass pathWithComponents: ([OFArray arrayWithObjects: @"sdmc:", @"foo", @"bar", @"baz", nil])], @"sdmc:/foo/bar/baz"); OTAssertEqualObjects([self.stringClass pathWithComponents: ([OFArray arrayWithObjects: @"foo/", @"bar/", @"", @"baz", @"/", nil])], @"foo/bar/baz"); OTAssertEqualObjects([self.stringClass pathWithComponents: [OFArray arrayWithObject: @"foo"]], @"foo"); OTAssertEqualObjects([self.stringClass pathWithComponents: [OFArray arrayWithObject: @"sdmc:"]], @"sdmc:/"); # else OTAssertEqualObjects([self.stringClass pathWithComponents: ([OFArray arrayWithObjects: @"/", @"foo", @"bar", @"baz", nil])], @"/foo/bar/baz"); OTAssertEqualObjects([self.stringClass pathWithComponents: ([OFArray arrayWithObjects: @"foo", @"bar", @"baz", nil])], @"foo/bar/baz"); OTAssertEqualObjects([self.stringClass pathWithComponents: ([OFArray arrayWithObjects: @"foo/", @"bar", @"", @"baz", @"/", nil])], @"foo/bar/baz"); OTAssertEqualObjects([self.stringClass pathWithComponents: [OFArray arrayWithObject: @"foo"]], @"foo"); # endif } - (void)testPathComponents { # if defined(OF_WINDOWS) || defined(OF_MSDOS) OTAssertEqualObjects([[self.stringClass stringWithString: @"c:/tmp"] pathComponents], ([OFArray arrayWithObjects: @"c:/", @"tmp", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @"c:\\tmp\\"] pathComponents], ([OFArray arrayWithObjects: @"c:\\", @"tmp", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @"c:\\"] pathComponents], [OFArray arrayWithObject: @"c:\\"]); OTAssertEqualObjects([[self.stringClass stringWithString: @"c:/"] pathComponents], [OFArray arrayWithObject: @"c:/"]); OTAssertEqualObjects([[self.stringClass stringWithString: @"c:"] pathComponents], [OFArray arrayWithObject: @"c:"]); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo\\bar"] pathComponents], ([OFArray arrayWithObjects: @"foo", @"bar", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo\\bar/baz/"] pathComponents], ([OFArray arrayWithObjects: @"foo", @"bar", @"baz", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo\\/"] pathComponents], [OFArray arrayWithObject: @"foo"]); # ifdef OF_WINDOWS OTAssertEqualObjects([[self.stringClass stringWithString: @"\\\\foo\\bar"] pathComponents], ([OFArray arrayWithObjects: @"\\\\", @"foo", @"bar", nil])); # endif OTAssertEqualObjects([[self.stringClass stringWithString: @""] pathComponents], [OFArray array]); # elif defined(OF_AMIGAOS) OTAssertEqualObjects([[self.stringClass stringWithString: @"dh0:tmp"] pathComponents], ([OFArray arrayWithObjects: @"dh0:", @"tmp", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @"dh0:tmp/"] pathComponents], ([OFArray arrayWithObjects: @"dh0:", @"tmp", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @"dh0:/"] pathComponents], ([OFArray arrayWithObjects: @"dh0:", @"/", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar"] pathComponents], ([OFArray arrayWithObjects: @"foo", @"bar", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar/baz/"] pathComponents], ([OFArray arrayWithObjects: @"foo", @"bar", @"baz", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo//"] pathComponents], ([OFArray arrayWithObjects: @"foo", @"/", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @""] pathComponents], [OFArray array]); # elif defined(OF_NINTENDO_3DS) || defined(OF_WII) || \ defined(OF_NINTENDO_SWITCH) OTAssertEqualObjects([[self.stringClass stringWithString: @"sdmc:/tmp"] pathComponents], ([OFArray arrayWithObjects: @"sdmc:", @"tmp", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @"sdmc:/"] pathComponents], [OFArray arrayWithObject: @"sdmc:"]); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar"] pathComponents], ([OFArray arrayWithObjects: @"foo", @"bar", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar/baz/"] pathComponents], ([OFArray arrayWithObjects: @"foo", @"bar", @"baz", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo//"] pathComponents], [OFArray arrayWithObject: @"foo"]); OTAssertEqualObjects([[self.stringClass stringWithString: @""] pathComponents], [OFArray array]); # else OTAssertEqualObjects([[self.stringClass stringWithString: @"/tmp"] pathComponents], ([OFArray arrayWithObjects: @"/", @"tmp", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @"/tmp/"] pathComponents], ([OFArray arrayWithObjects: @"/", @"tmp", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @"/"] pathComponents], [OFArray arrayWithObject: @"/"]); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar"] pathComponents], ([OFArray arrayWithObjects: @"foo", @"bar", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar/baz/"] pathComponents], ([OFArray arrayWithObjects: @"foo", @"bar", @"baz", nil])); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo//"] pathComponents], [OFArray arrayWithObject: @"foo"]); OTAssertEqualObjects([[self.stringClass stringWithString: @""] pathComponents], [OFArray array]); # endif } - (void)testLastPathComponent { # if defined(OF_WINDOWS) || defined(OF_MSDOS) OTAssertEqualObjects([[self.stringClass stringWithString: @"c:/tmp"] lastPathComponent], @"tmp"); OTAssertEqualObjects([[self.stringClass stringWithString: @"c:\\tmp\\"] lastPathComponent], @"tmp"); OTAssertEqualObjects([[self.stringClass stringWithString: @"c:\\"] lastPathComponent], @"c:\\"); OTAssertEqualObjects([[self.stringClass stringWithString: @"c:/"] lastPathComponent], @"c:/"); OTAssertEqualObjects([[self.stringClass stringWithString: @"\\"] lastPathComponent], @"\\"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo"] lastPathComponent], @"foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo\\bar"] lastPathComponent], @"bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar/baz/"] lastPathComponent], @"baz"); # ifdef OF_WINDOWS OTAssertEqualObjects([[self.stringClass stringWithString: @"\\\\foo\\bar"] lastPathComponent], @"bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"\\\\"] lastPathComponent], @"\\\\"); # endif # elif defined(OF_AMIGAOS) OTAssertEqualObjects([[self.stringClass stringWithString: @"dh0:tmp"] lastPathComponent], @"tmp"); OTAssertEqualObjects([[self.stringClass stringWithString: @"dh0:tmp/"] lastPathComponent], @"tmp"); OTAssertEqualObjects([[self.stringClass stringWithString: @"dh0:/"] lastPathComponent], @"/"); OTAssertEqualObjects([[self.stringClass stringWithString: @"dh0:"] lastPathComponent], @"dh0:"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo"] lastPathComponent], @"foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar"] lastPathComponent], @"bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar/baz/"] lastPathComponent], @"baz"); # elif defined(OF_WII) || defined(OF_NINTENDO_DS) || \ defined(OF_NINTENDO_3DS) || defined(OF_NINTENDO_SWITCH) OTAssertEqualObjects([[self.stringClass stringWithString: @"sdmc:/tmp"] lastPathComponent], @"tmp"); OTAssertEqualObjects([[self.stringClass stringWithString: @"sdmc:/tmp/"] lastPathComponent], @"tmp"); OTAssertEqualObjects([[self.stringClass stringWithString: @"sdmc:/"] lastPathComponent], @"sdmc:/"); OTAssertEqualObjects([[self.stringClass stringWithString: @"sdmc:"] lastPathComponent], @"sdmc:"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo"] lastPathComponent], @"foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar"] lastPathComponent], @"bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar/baz/"] lastPathComponent], @"baz"); # else OTAssertEqualObjects([[self.stringClass stringWithString: @"/tmp"] lastPathComponent], @"tmp"); OTAssertEqualObjects([[self.stringClass stringWithString: @"/tmp/"] lastPathComponent], @"tmp"); OTAssertEqualObjects([[self.stringClass stringWithString: @"/"] lastPathComponent], @"/"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo"] lastPathComponent], @"foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar"] lastPathComponent], @"bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar/baz/"] lastPathComponent], @"baz"); # endif } - (void)testPathExtension { OTAssertEqualObjects([[self.stringClass stringWithString: @"foo.bar"] pathExtension], @"bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/.bar"] pathExtension], @""); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/.bar.baz"] pathExtension], @"baz"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar.baz/"] pathExtension], @"baz"); } - (void)testStringByDeletingLastPathComponent { # if defined(OF_WINDOWS) || defined(OF_MSDOS) OTAssertEqualObjects([[self.stringClass stringWithString: @"\\tmp"] stringByDeletingLastPathComponent], @"\\"); OTAssertEqualObjects([[self.stringClass stringWithString: @"/tmp/"] stringByDeletingLastPathComponent], @"/"); OTAssertEqualObjects([[self.stringClass stringWithString: @"c:\\"] stringByDeletingLastPathComponent], @"c:\\"); OTAssertEqualObjects([[self.stringClass stringWithString: @"c:/"] stringByDeletingLastPathComponent], @"c:/"); OTAssertEqualObjects([[self.stringClass stringWithString: @"c:\\tmp/foo/"] stringByDeletingLastPathComponent], @"c:\\tmp"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo\\bar"] stringByDeletingLastPathComponent], @"foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @"\\"] stringByDeletingLastPathComponent], @"\\"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo"] stringByDeletingLastPathComponent], @"."); # ifdef OF_WINDOWS OTAssertEqualObjects([[self.stringClass stringWithString: @"\\\\foo\\bar"] stringByDeletingLastPathComponent], @"\\\\foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @"\\\\foo"] stringByDeletingLastPathComponent], @"\\\\"); OTAssertEqualObjects([[self.stringClass stringWithString: @"\\\\"] stringByDeletingLastPathComponent], @"\\\\"); # endif # elif defined(OF_AMIGAOS) OTAssertEqualObjects([[self.stringClass stringWithString: @"dh0:"] stringByDeletingLastPathComponent], @"dh0:"); OTAssertEqualObjects([[self.stringClass stringWithString: @"dh0:tmp"] stringByDeletingLastPathComponent], @"dh0:"); OTAssertEqualObjects([[self.stringClass stringWithString: @"dh0:tmp/"] stringByDeletingLastPathComponent], @"dh0:"); OTAssertEqualObjects([[self.stringClass stringWithString: @"dh0:/"] stringByDeletingLastPathComponent], @"dh0:"); OTAssertEqualObjects([[self.stringClass stringWithString: @"dh0:tmp/foo/"] stringByDeletingLastPathComponent], @"dh0:tmp"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar"] stringByDeletingLastPathComponent], @"foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo"] stringByDeletingLastPathComponent], @""); # elif defined(OF_NINTENDO_3DS) || defined(OF_WII) || \ defined(OF_NINTENDO_SWITCH) OTAssertEqualObjects([[self.stringClass stringWithString: @"/tmp/"] stringByDeletingLastPathComponent], @""); OTAssertEqualObjects([[self.stringClass stringWithString: @"sdmc:/tmp/foo/"] stringByDeletingLastPathComponent], @"sdmc:/tmp"); OTAssertEqualObjects([[self.stringClass stringWithString: @"sdmc:/"] stringByDeletingLastPathComponent], @"sdmc:/"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar"] stringByDeletingLastPathComponent], @"foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @"/"] stringByDeletingLastPathComponent], @""); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo"] stringByDeletingLastPathComponent], @"."); # else OTAssertEqualObjects([[self.stringClass stringWithString: @"/tmp"] stringByDeletingLastPathComponent], @"/"); OTAssertEqualObjects([[self.stringClass stringWithString: @"/tmp/"] stringByDeletingLastPathComponent], @"/"); OTAssertEqualObjects([[self.stringClass stringWithString: @"/tmp/foo/"] stringByDeletingLastPathComponent], @"/tmp"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo/bar"] stringByDeletingLastPathComponent], @"foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @"/"] stringByDeletingLastPathComponent], @"/"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo"] stringByDeletingLastPathComponent], @"."); # endif } - (void)testStringByDeletingPathExtension { # if defined(OF_WINDOWS) || defined(OF_MSDOS) OTAssertEqualObjects([[self.stringClass stringWithString: @"foo.bar"] stringByDeletingPathExtension], @"foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo..bar"] stringByDeletingPathExtension], @"foo."); OTAssertEqualObjects([[self.stringClass stringWithString: @"c:/foo.\\bar"] stringByDeletingPathExtension], @"c:/foo.\\bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"c:\\foo./bar.baz"] stringByDeletingPathExtension], @"c:\\foo.\\bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo.bar/"] stringByDeletingPathExtension], @"foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @".foo"] stringByDeletingPathExtension], @".foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @".foo.bar"] stringByDeletingPathExtension], @".foo"); # elif defined(OF_AMIGAOS) OTAssertEqualObjects([[self.stringClass stringWithString: @"foo.bar"] stringByDeletingPathExtension], @"foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo..bar"] stringByDeletingPathExtension], @"foo."); OTAssertEqualObjects([[self.stringClass stringWithString: @"dh0:foo.bar"] stringByDeletingPathExtension], @"dh0:foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @"dh0:foo./bar"] stringByDeletingPathExtension], @"dh0:foo./bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"dh0:foo./bar.baz"] stringByDeletingPathExtension], @"dh0:foo./bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo.bar/"] stringByDeletingPathExtension], @"foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @".foo"] stringByDeletingPathExtension], @".foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @".foo\\bar"] stringByDeletingPathExtension], @".foo\\bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @".foo.bar"] stringByDeletingPathExtension], @".foo"); # elif defined(OF_WII) || defined(OF_NINTENDO_DS) || \ defined(OF_NINTENDO_3DS) || defined(OF_NINTENDO_SWITCH) OTAssertEqualObjects([[self.stringClass stringWithString: @"foo.bar"] stringByDeletingPathExtension], @"foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo..bar"] stringByDeletingPathExtension], @"foo."); OTAssertEqualObjects([[self.stringClass stringWithString: @"sdmc:/foo./bar"] stringByDeletingPathExtension], @"sdmc:/foo./bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"sdmc:/foo./bar.baz"] stringByDeletingPathExtension], @"sdmc:/foo./bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo.bar/"] stringByDeletingPathExtension], @"foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @".foo"] stringByDeletingPathExtension], @".foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @".foo.bar"] stringByDeletingPathExtension], @".foo"); # else OTAssertEqualObjects([[self.stringClass stringWithString: @"foo.bar"] stringByDeletingPathExtension], @"foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo..bar"] stringByDeletingPathExtension], @"foo."); OTAssertEqualObjects([[self.stringClass stringWithString: @"/foo./bar"] stringByDeletingPathExtension], @"/foo./bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"/foo./bar.baz"] stringByDeletingPathExtension], @"/foo./bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @"foo.bar/"] stringByDeletingPathExtension], @"foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @".foo"] stringByDeletingPathExtension], @".foo"); OTAssertEqualObjects([[self.stringClass stringWithString: @".foo\\bar"] stringByDeletingPathExtension], @".foo\\bar"); OTAssertEqualObjects([[self.stringClass stringWithString: @".foo.bar"] stringByDeletingPathExtension], @".foo"); # endif } # if defined(OF_WINDOWS) || defined(OF_MSDOS) - (void)testStringByStandardizingPath { /* TODO: Add more tests */ OTAssertEqualObjects([[self.stringClass stringWithString: @"c:\\..\\asd"] stringByStandardizingPath], @"c:\\..\\asd"); # ifndef OF_MSDOS OTAssertEqualObjects([[self.stringClass stringWithString: @"\\\\foo\\..\\bar\\qux"] stringByStandardizingPath], @"\\\\bar\\qux"); # endif } # endif #endif @end @implementation CustomString - (instancetype)init { self = [super init]; @try { _string = [[OFMutableString alloc] init]; } @catch (id e) { [self release]; @throw e; } return self; } - (instancetype)initWithString: (OFString *)string { self = [super init]; @try { _string = [string mutableCopy]; } @catch (id e) { [self release]; @throw e; } return self; } - (instancetype)initWithCString: (const char *)cString encoding: (OFStringEncoding)encoding length: (size_t)length { self = [super init]; @try { _string = [[OFMutableString alloc] initWithCString: cString encoding: encoding length: length]; } @catch (id e) { [self release]; @throw e; } return self; } - (instancetype)initWithUTF16String: (const OFChar16 *)UTF16String length: (size_t)length byteOrder: (OFByteOrder)byteOrder { self = [super init]; @try { _string = [[OFMutableString alloc] initWithUTF16String: UTF16String length: length byteOrder: byteOrder]; } @catch (id e) { [self release]; @throw e; } return self; } - (instancetype)initWithUTF32String: (const OFChar32 *)UTF32String length: (size_t)length byteOrder: (OFByteOrder)byteOrder { self = [super init]; @try { _string = [[OFMutableString alloc] initWithUTF32String: UTF32String length: length byteOrder: byteOrder]; } @catch (id e) { [self release]; @throw e; } return self; } - (instancetype)initWithFormat: (OFConstantString *)format arguments: (va_list)arguments { self = [super init]; @try { _string = [[OFMutableString alloc] initWithFormat: format arguments: arguments]; } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { [_string release]; [super dealloc]; } - (OFUnichar)characterAtIndex: (size_t)idx { return [_string characterAtIndex: idx]; } - (size_t)length { return _string.length; } @end @implementation EntityHandler - (OFString *)string: (OFString *)string containsUnknownEntityNamed: (OFString *)entity { if ([entity isEqual: @"foo"]) return @"bar"; return nil; } @end