Index: src/OFMutableURL.h ================================================================== --- src/OFMutableURL.h +++ src/OFMutableURL.h @@ -52,10 +52,18 @@ /*! * The path part of the URL. */ @property OF_NULLABLE_PROPERTY (readwrite, copy, nonatomic) OFString *path; +/*! + * The path of the URL split into components. + * + * The first component must always be empty to designate the root. + */ +@property OF_NULLABLE_PROPERTY (readwrite, copy, nonatomic) + OFArray OF_GENERIC(OFString *) *pathComponents; + /*! * The parameters part of the URL. */ @property OF_NULLABLE_PROPERTY (readwrite, copy, nonatomic) OFString *parameters; @@ -82,22 +90,12 @@ * * @return An initialized OFMutableURL */ - (instancetype)init; -/*! - * @brief Sets the URL's path from the specified path components. - * - * The first component must always be empty to designate the root. - * - * @param components The path components to set the URL's path from - */ -- (void)setPathComponents: - (nullable OFArray OF_GENERIC(OFString *) *)components; - /*! * @brief Converts the mutable URL to an immutable URL. */ - (void)makeImmutable; @end OF_ASSUME_NONNULL_END Index: src/OFMutableURL.m ================================================================== --- src/OFMutableURL.m +++ src/OFMutableURL.m @@ -23,11 +23,12 @@ #import "OFURL+Private.h" #import "OFInvalidFormatException.h" @implementation OFMutableURL -@dynamic scheme, host, port, user, password, path, parameters, query, fragment; +@dynamic scheme, host, port, user, password, path, pathComponents, parameters; +@dynamic query, fragment; + (instancetype)URL { return [[[self alloc] init] autorelease]; } @@ -76,10 +77,30 @@ { OFString *old = _path; _path = [path copy]; [old release]; } + +- (void)setPathComponents: (OFArray *)components +{ + void *pool = objc_autoreleasePoolPush(); + + if (components == nil) { + [self setPath: nil]; + return; + } + + if ([components count] == 0) + @throw [OFInvalidFormatException exception]; + + if ([[components firstObject] length] != 0) + @throw [OFInvalidFormatException exception]; + + [self setPath: [components componentsJoinedByString: @"/"]]; + + objc_autoreleasePoolPop(pool); +} - (void)setParameters: (OFString *)parameters { OFString *old = _parameters; _parameters = [parameters copy]; @@ -98,30 +119,10 @@ OFString *old = _fragment; _fragment = [fragment copy]; [old release]; } -- (void)setPathComponents: (OFArray *)components -{ - void *pool = objc_autoreleasePoolPush(); - - if (components == nil) { - [self setPath: nil]; - return; - } - - if ([components count] == 0) - @throw [OFInvalidFormatException exception]; - - if ([[components firstObject] length] != 0) - @throw [OFInvalidFormatException exception]; - - [self setPath: [components componentsJoinedByString: @"/"]]; - - objc_autoreleasePoolPop(pool); -} - - (id)copy { OFMutableURL *copy = [self mutableCopy]; [copy makeImmutable]; Index: src/OFURL.h ================================================================== --- src/OFURL.h +++ src/OFURL.h @@ -65,10 +65,26 @@ /*! * The path part of the URL. */ @property OF_NULLABLE_PROPERTY (readonly, copy, nonatomic) OFString *path; +/*! + * The path of the URL split into components. + * + * The first component must always be empty to designate the root. + */ +@property OF_NULLABLE_PROPERTY (readonly, copy, nonatomic) + OFArray OF_GENERIC(OFString *) *pathComponents; + +/*! + * The last path component of the URL. + * + * Returns the empty string if the path is the root. + */ +@property OF_NULLABLE_PROPERTY (readonly, copy, nonatomic) + OFString *lastPathComponent; + /*! * The parameters part of the URL. */ @property OF_NULLABLE_PROPERTY (readonly, copy, nonatomic) OFString *parameters; @@ -177,19 +193,10 @@ * * @return The URL as a string */ - (OFString *)string; -/*! - * @brief Returns the path of the URL split into components. - * - * The first component is always empty to designate the root. - * - * @return The path of the URL split into components - */ -- (nullable OFArray OF_GENERIC(OFString *) *)pathComponents; - /*! * @brief Returns the local file system representation for a file URL. * * This only exists for URLs with the file scheme and throws an exception * otherwise. Index: src/OFURL.m ================================================================== --- src/OFURL.m +++ src/OFURL.m @@ -438,10 +438,55 @@ - (OFString *)path { return _path; } + +- (OFArray *)pathComponents +{ + return [_path componentsSeparatedByString: @"/"]; +} + +- (OFString *)lastPathComponent +{ + void *pool; + OFString *path; + const char *UTF8String, *lastComponent; + size_t length; + OFString *ret; + + if (_path == nil) + return nil; + + if ([_path isEqual: @"/"]) + return @""; + + pool = objc_autoreleasePoolPush(); + path = _path; + + if ([path hasSuffix: @"/"]) + path = [path substringWithRange: + of_range(0, [path length] - 1)]; + + UTF8String = lastComponent = [path UTF8String]; + length = [path UTF8StringLength]; + + for (size_t i = 1; i <= length; i++) { + if (UTF8String[length - i] == '/') { + lastComponent = UTF8String + (length - i) + 1; + break; + } + } + + ret = [[OFString alloc] + initWithUTF8String: lastComponent + length: length - (lastComponent - UTF8String)]; + + objc_autoreleasePoolPop(pool); + + return [ret autorelease]; +} - (OFString *)parameters { return _parameters; } @@ -521,15 +566,10 @@ [ret makeImmutable]; return ret; } -- (OFArray *)pathComponents -{ - return [_path componentsSeparatedByString: @"/"]; -} - - (OFString *)fileSystemRepresentation { void *pool = objc_autoreleasePoolPush(); OFString *path; @@ -537,11 +577,11 @@ @throw [OFInvalidArgumentException exception]; if (![_path hasPrefix: @"/"]) @throw [OFInvalidFormatException exception]; - path = [[_path copy] autorelease]; + path = _path; if ([path hasSuffix: @"/"]) path = [path substringWithRange: of_range(0, [path length] - 1)]; Index: tests/OFURLTests.m ================================================================== --- tests/OFURLTests.m +++ tests/OFURLTests.m @@ -15,10 +15,11 @@ */ #include "config.h" #import "OFURL.h" +#import "OFArray.h" #ifdef OF_HAVE_FILES # import "OFFileManager.h" #endif #import "OFNumber.h" #import "OFString.h" @@ -79,10 +80,24 @@ TEST(@"-[host]", [[u1 host] isEqual: @"ho%3Ast"] && [u4 port] == 0) TEST(@"-[port]", [[u1 port] isEqual: [OFNumber numberWithUInt16: 1234]]) TEST(@"-[path]", [[u1 path] isEqual: @"/pa%3Bth"] && [[u4 path] isEqual: @"/etc/passwd"]) + TEST(@"-[pathComponents]", + [[u1 pathComponents] isEqual: + [OFArray arrayWithObjects: @"", @"pa%3Bth", nil]] && + [[u4 pathComponents] isEqual: + [OFArray arrayWithObjects: @"", @"etc", @"passwd", nil]]) + TEST(@"-[lastPathComponent", + [[[OFURL URLWithString: @"http://host/foo//bar/baz"] + lastPathComponent] isEqual: @"baz"] && + [[[OFURL URLWithString: @"http://host/foo//bar/baz/"] + lastPathComponent] isEqual: @"baz"] && + [[[OFURL URLWithString: @"http://host/foo/"] + lastPathComponent] isEqual: @"foo"] && + [[[OFURL URLWithString: @"http://host/"] + lastPathComponent] isEqual: @""]) TEST(@"-[parameters]", [[u1 parameters] isEqual: @"pa%3Fram"] && [u4 parameters] == nil) TEST(@"-[query]", [[u1 query] isEqual: @"que%23ry"] && [u4 query] == nil) TEST(@"-[fragment]",