Index: src/OFConstantString.m ================================================================== --- src/OFConstantString.m +++ src/OFConstantString.m @@ -540,10 +540,28 @@ [self finishInitialization]; return [self componentsSeparatedByString: delimiter options: options]; } + +- (OFArray *) + componentsSeparatedByCharactersInSet: (OFCharacterSet *)characterSet +{ + [self finishInitialization]; + + return [self componentsSeparatedByCharactersInSet: characterSet]; +} + +- (OFArray *) + componentsSeparatedByCharactersInSet: (OFCharacterSet *)characterSet + options: (int)options +{ + [self finishInitialization]; + + return [self componentsSeparatedByCharactersInSet: characterSet + options: options]; +} - (OFArray *)pathComponents { [self finishInitialization]; Index: src/OFString.h ================================================================== --- src/OFString.h +++ src/OFString.h @@ -1100,20 +1100,22 @@ * @return A boolean whether the string has the specified suffix */ - (bool)hasSuffix: (OFString *)suffix; /*! - * @brief Separates an OFString into an OFArray of OFStrings. + * @brief Separates the string into an array of strings, split by the specified + * delimiter. * * @param delimiter The delimiter for separating * @return An autoreleased OFArray with the separated string */ -- (OFArray OF_GENERIC(OFString *) *)componentsSeparatedByString: - (OFString *)delimiter; +- (OFArray OF_GENERIC(OFString *) *) + componentsSeparatedByString: (OFString *)delimiter; /*! - * @brief Separates an OFString into an OFArray of OFStrings. + * @brief Separates the string into an array of strings, split by the specified + * delimiter. * * @param delimiter The delimiter for separating * @param options Options according to which the string should be separated.@n * Possible values are: * Value | Description @@ -1123,10 +1125,36 @@ */ - (OFArray OF_GENERIC(OFString *) *) componentsSeparatedByString: (OFString *)delimiter options: (int)options; +/*! + * @brief Separates the string into an array of strings, split by characters in + * the specified set. + * + * @param characterSet The character set for separating + * @return An autoreleased OFArray with the separated string + */ +- (OFArray OF_GENERIC(OFString *) *) + componentsSeparatedByCharactersInSet: (OFCharacterSet *)characterSet; + +/*! + * @brief Separates the string into an array of strings, split by characters in + * the specified set. + * + * @param characterSet The character set for separating + * @param options Options according to which the string should be separated.@n + * Possible values are: + * Value | Description + * -----------------------|---------------------- + * `OF_STRING_SKIP_EMPTY` | Skip empty components + * @return An autoreleased OFArray with the separated string + */ +- (OFArray OF_GENERIC(OFString *) *) + componentsSeparatedByCharactersInSet: (OFCharacterSet *)characterSet + options: (int)options; + /*! * @brief Returns the string in UTF-16 encoding with the specified byte order. * * The result is valid until the autorelease pool is released. If you want to * use the result outside the scope of the current autorelease pool, you have to Index: src/OFString.m ================================================================== --- src/OFString.m +++ src/OFString.m @@ -2271,10 +2271,57 @@ last = i + 1; } component = [self substringWithRange: of_range(last, length - last)]; if (!skipEmpty || [component length] > 0) [array addObject: component]; + + [array makeImmutable]; + + objc_autoreleasePoolPop(pool); + + return array; +} + +- (OFArray *) + componentsSeparatedByCharactersInSet: (OFCharacterSet *)characterSet +{ + return [self componentsSeparatedByCharactersInSet: characterSet + options: 0]; +} + +- (OFArray *) + componentsSeparatedByCharactersInSet: (OFCharacterSet *)characterSet + options: (int)options +{ + OFMutableArray *array = [OFMutableArray array]; + void *pool = objc_autoreleasePoolPush(); + bool skipEmpty = (options & OF_STRING_SKIP_EMPTY); + const of_unichar_t *characters = [self characters]; + size_t length = [self length]; + bool (*characterIsMember)(id, SEL, of_unichar_t) = + (bool (*)(id, SEL, of_unichar_t))[characterSet + methodForSelector: @selector(characterIsMember:)]; + size_t last; + + last = 0; + for (size_t i = 0; i < length; i++) { + if (characterIsMember(characterSet, + @selector(characterIsMember:), characters[i])) { + if (!skipEmpty || i != last) { + OFString *component = [self substringWithRange: + of_range(last, i - last)]; + [array addObject: component]; + } + + last = i + 1; + } + } + if (!skipEmpty || length != last) { + OFString *component = [self substringWithRange: + of_range(last, length - last)]; + [array addObject: component]; + } [array makeImmutable]; objc_autoreleasePoolPop(pool); Index: tests/OFStringTests.m ================================================================== --- tests/OFStringTests.m +++ tests/OFStringTests.m @@ -222,11 +222,11 @@ { OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; OFMutableString *s[3]; OFString *is; OFArray *a; - int i; + size_t i; const of_unichar_t *ua; const uint16_t *u16a; OFCharacterSet *cs; EntityHandler *h; #ifdef OF_HAVE_BLOCKS @@ -604,27 +604,57 @@ ![C(@"foobar") hasSuffix: @"foobar0"]) i = 0; TEST(@"-[componentsSeparatedByString:]", (a = [C(@"fooXXbarXXXXbazXXXX") - componentsSeparatedByString: @"XX"]) && [a count] == 6 && + componentsSeparatedByString: @"XX"]) && [[a objectAtIndex: i++] isEqual: @"foo"] && [[a objectAtIndex: i++] isEqual: @"bar"] && [[a objectAtIndex: i++] isEqual: @""] && [[a objectAtIndex: i++] isEqual: @"baz"] && [[a objectAtIndex: i++] isEqual: @""] && - [[a objectAtIndex: i++] isEqual: @""]) + [[a objectAtIndex: i++] isEqual: @""] && + [a count] == i) i = 0; TEST(@"-[componentsSeparatedByString:options:]", (a = [C(@"fooXXbarXXXXbazXXXX") componentsSeparatedByString: @"XX" options: OF_STRING_SKIP_EMPTY]) && - [a count] == 3 && + [[a objectAtIndex: i++] isEqual: @"foo"] && + [[a objectAtIndex: i++] isEqual: @"bar"] && + [[a objectAtIndex: i++] isEqual: @"baz"] && + [a count] == i) + + cs = [OFCharacterSet characterSetWithCharactersInString: @"XYZ"]; + + i = 0; + TEST(@"-[componentsSeparatedByCharactersInSet:]", + (a = [C(@"fooXYbarXYZXbazXYXZx") + componentsSeparatedByCharactersInSet: cs]) && + [[a objectAtIndex: i++] isEqual: @"foo"] && + [[a objectAtIndex: i++] isEqual: @""] && + [[a objectAtIndex: i++] isEqual: @"bar"] && + [[a objectAtIndex: i++] isEqual: @""] && + [[a objectAtIndex: i++] isEqual: @""] && + [[a objectAtIndex: i++] isEqual: @""] && + [[a objectAtIndex: i++] isEqual: @"baz"] && + [[a objectAtIndex: i++] isEqual: @""] && + [[a objectAtIndex: i++] isEqual: @""] && + [[a objectAtIndex: i++] isEqual: @""] && + [[a objectAtIndex: i++] isEqual: @"x"] && + [a count] == i) + + i = 0; + TEST(@"-[componentsSeparatedByCharactersInSet:options:]", + (a = [C(@"fooXYbarXYZXbazXYXZ") + componentsSeparatedByCharactersInSet: cs + options: OF_STRING_SKIP_EMPTY]) && [[a objectAtIndex: i++] isEqual: @"foo"] && [[a objectAtIndex: i++] isEqual: @"bar"] && - [[a objectAtIndex: i++] isEqual: @"baz"]) + [[a objectAtIndex: i++] isEqual: @"baz"] && + [a count] == i) #ifdef OF_HAVE_FILES # if defined(OF_WINDOWS) || defined(OF_MSDOS) TEST(@"+[pathWithComponents:]", [[stringClass pathWithComponents: [OFArray arrayWithObjects: