Index: src/OFString+PathAdditions_UNIX.m ================================================================== --- src/OFString+PathAdditions_UNIX.m +++ src/OFString+PathAdditions_UNIX.m @@ -25,11 +25,28 @@ int _OFString_PathAdditions_reference; @implementation OFString (PathAdditions) + (OFString *)pathWithComponents: (OFArray *)components { - return [components componentsJoinedByString: @"/"]; + void *pool = objc_autoreleasePoolPush(); + OFString *ret; + + if ([[components firstObject] isEqual: @"/"]) { + OFMutableArray OF_GENERIC(OFString *) *mutableComponents = + [[components mutableCopy] autorelease]; + + [mutableComponents replaceObjectAtIndex: 0 + withObject: @""]; + + components = mutableComponents; + } + + ret = [components componentsJoinedByString: @"/"]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; } - (bool)isAbsolutePath { return [self hasPrefix: @"/"]; @@ -58,10 +75,14 @@ last = i + 1; } } [ret addObject: [OFString stringWithUTF8String: cString + last length: i - last]]; + + if ([[ret firstObject] isEqual: @""]) + [ret replaceObjectAtIndex: 0 + withObject: @"/"]; [ret makeImmutable]; objc_autoreleasePoolPop(pool); @@ -210,20 +231,33 @@ } - (OFString *)stringByStandardizingPath { void *pool = objc_autoreleasePoolPush(); - OFArray OF_GENERIC(OFString *) *components = [self pathComponents]; + OFArray OF_GENERIC(OFString *) *components; OFMutableArray OF_GENERIC(OFString *) *array; - OFString *ret; - bool done = false, startsWithEmpty, endsWithEmpty; + OFString *firstComponent, *ret; + bool done = false, startsWithSlash, endsWithEmpty; + + if ([self length] == 0) + return @""; + + components = [self pathComponents]; + + if ([components count] == 1) { + objc_autoreleasePoolPop(pool); + return self; + } array = [[components mutableCopy] autorelease]; + firstComponent = [array firstObject]; + startsWithSlash = + ([firstComponent isEqual: @"/"] || [firstComponent length] == 0); + endsWithEmpty = ([[array lastObject] length] == 0); - if ((startsWithEmpty = [[array firstObject] isEqual: @""])) + if (startsWithSlash) [array removeObjectAtIndex: 0]; - endsWithEmpty = [[array lastObject] isEqual: @""]; while (!done) { size_t length = [array count]; done = true; @@ -250,13 +284,14 @@ break; } } } - if (startsWithEmpty) + if (startsWithSlash) [array insertObject: @"" atIndex: 0]; + if (endsWithEmpty) [array addObject: @""]; ret = [[array componentsJoinedByString: @"/"] retain]; Index: src/OFURL.m ================================================================== --- src/OFURL.m +++ src/OFURL.m @@ -964,12 +964,12 @@ path = [path substringWithRange: of_range(0, [path length] - 1)]; #if defined(OF_WINDOWS) || defined(OF_MSDOS) path = [path substringWithRange: of_range(1, [path length] - 1)]; - path = [OFString pathWithComponents: - [path componentsSeparatedByString: @"/"]]; + path = [path stringByReplacingOccurrencesOfString: @"/" + withString: @"\\"]; #endif [path retain]; objc_autoreleasePoolPop(pool); Index: tests/OFStringTests.m ================================================================== --- tests/OFStringTests.m +++ tests/OFStringTests.m @@ -619,36 +619,66 @@ [[a objectAtIndex: i++] isEqual: @"foo"] && [[a objectAtIndex: i++] isEqual: @"bar"] && [[a objectAtIndex: i++] isEqual: @"baz"]) #ifdef OF_HAVE_FILES -# if !defined(OF_WINDOWS) && !defined(OF_MSDOS) -# define EXPECTED @"foo/bar/baz" +# if defined(OF_WINDOWS) || defined(OF_MSDOS) + TEST(@"+[pathWithComponents:]", + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"foo", @"bar", @"baz", nil]] isEqual: @"foo\\bar\\baz"] && + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"foo", nil]] isEqual: @"foo"]) # else -# define EXPECTED @"foo\\bar\\baz" -# endif TEST(@"+[pathWithComponents:]", - (is = [stringClass pathWithComponents: [OFArray arrayWithObjects: - @"foo", @"bar", @"baz", nil]]) && - [is isEqual: EXPECTED] && - (is = [stringClass pathWithComponents: - [OFArray arrayWithObjects: @"foo", nil]]) && - [is isEqual: @"foo"]) -# undef EXPECTED - + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"/", @"foo", @"bar", @"baz", nil]] isEqual: @"/foo/bar/baz"] && + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"foo", @"bar", @"baz", nil]] isEqual: @"foo/bar/baz"] && + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"foo", nil]] isEqual: @"foo"]) +# endif + +# if defined(OF_WINDOWS) || defined(OF_MSDOS) + TEST(@"-[pathComponents]", + /* /tmp */ + (a = [C(@"c:/tmp") pathComponents]) && [a count] == 2 && + [[a objectAtIndex: 0] isEqual: @"c:"] && + [[a objectAtIndex: 1] isEqual: @"tmp"] && + /* /tmp/ */ + (a = [C(@"c:\\tmp\\") pathComponents]) && [a count] == 2 && + [[a objectAtIndex: 0] isEqual: @"c:"] && + [[a objectAtIndex: 1] isEqual: @"tmp"] && + /* / */ + (a = [C(@"c:/") pathComponents]) && [a count] == 1 && + [[a objectAtIndex: 0] isEqual: @"c:"] && + /* foo/bar */ + (a = [C(@"foo\\bar") pathComponents]) && [a count] == 2 && + [[a objectAtIndex: 0] isEqual: @"foo"] && + [[a objectAtIndex: 1] isEqual: @"bar"] && + /* foo/bar/baz/ */ + (a = [C(@"foo\\bar/baz") pathComponents]) && [a count] == 3 && + [[a objectAtIndex: 0] isEqual: @"foo"] && + [[a objectAtIndex: 1] isEqual: @"bar"] && + [[a objectAtIndex: 2] isEqual: @"baz"] && + /* foo// */ + (a = [C(@"foo\\/") pathComponents]) && [a count] == 2 && + [[a objectAtIndex: 0] isEqual: @"foo"] && + [[a objectAtIndex: 1] isEqual: @""] && + [[C(@"") pathComponents] count] == 0) +# else TEST(@"-[pathComponents]", /* /tmp */ (a = [C(@"/tmp") pathComponents]) && [a count] == 2 && - [[a objectAtIndex: 0] isEqual: @""] && + [[a objectAtIndex: 0] isEqual: @"/"] && [[a objectAtIndex: 1] isEqual: @"tmp"] && /* /tmp/ */ (a = [C(@"/tmp/") pathComponents]) && [a count] == 2 && - [[a objectAtIndex: 0] isEqual: @""] && + [[a objectAtIndex: 0] isEqual: @"/"] && [[a objectAtIndex: 1] isEqual: @"tmp"] && /* / */ (a = [C(@"/") pathComponents]) && [a count] == 1 && - [[a objectAtIndex: 0] isEqual: @""] && + [[a objectAtIndex: 0] isEqual: @"/"] && /* foo/bar */ (a = [C(@"foo/bar") pathComponents]) && [a count] == 2 && [[a objectAtIndex: 0] isEqual: @"foo"] && [[a objectAtIndex: 1] isEqual: @"bar"] && /* foo/bar/baz/ */ @@ -659,56 +689,57 @@ /* foo// */ (a = [C(@"foo//") pathComponents]) && [a count] == 2 && [[a objectAtIndex: 0] isEqual: @"foo"] && [[a objectAtIndex: 1] isEqual: @""] && [[C(@"") pathComponents] count] == 0) +# endif -#if !defined(OF_WINDOWS) && !defined(OF_MSDOS) +# if !defined(OF_WINDOWS) && !defined(OF_MSDOS) TEST(@"-[lastPathComponent]", [[C(@"/tmp") lastPathComponent] isEqual: @"tmp"] && [[C(@"/tmp/") lastPathComponent] isEqual: @"tmp"] && [[C(@"/") lastPathComponent] isEqual: @"/"] && [[C(@"foo") lastPathComponent] isEqual: @"foo"] && [[C(@"foo/bar") lastPathComponent] isEqual: @"bar"] && [[C(@"foo/bar/baz/") lastPathComponent] isEqual: @"baz"]) -#else +# else TEST(@"-[lastPathComponent]", [[C(@"c:/tmp") lastPathComponent] isEqual: @"tmp"] && [[C(@"c:\\tmp\\") lastPathComponent] isEqual: @"tmp"] && [[C(@"\\") lastPathComponent] isEqual: @""] && [[C(@"foo") lastPathComponent] isEqual: @"foo"] && [[C(@"foo\\bar") lastPathComponent] isEqual: @"bar"] && [[C(@"foo/bar/baz/") lastPathComponent] isEqual: @"baz"]) -#endif +# endif TEST(@"-[pathExtension]", [[C(@"foo.bar") pathExtension] isEqual: @"bar"] && [[C(@"foo/.bar") pathExtension] isEqual: @""] && [[C(@"foo/.bar.baz") pathExtension] isEqual: @"baz"] && [[C(@"foo/bar.baz/") pathExtension] isEqual: @"baz"]) -#if !defined(OF_WINDOWS) && !defined(OF_MSDOS) +# if !defined(OF_WINDOWS) && !defined(OF_MSDOS) TEST(@"-[stringByDeletingLastPathComponent]", [[C(@"/tmp") stringByDeletingLastPathComponent] isEqual: @"/"] && [[C(@"/tmp/") stringByDeletingLastPathComponent] isEqual: @"/"] && [[C(@"/tmp/foo/") stringByDeletingLastPathComponent] isEqual: @"/tmp"] && [[C(@"foo/bar") stringByDeletingLastPathComponent] isEqual: @"foo"] && [[C(@"/") stringByDeletingLastPathComponent] isEqual: @"/"] && [[C(@"foo") stringByDeletingLastPathComponent] isEqual: @"."]) -#else +# else TEST(@"-[stringByDeletingLastPathComponent]", [[C(@"\\tmp") stringByDeletingLastPathComponent] isEqual: @""] && [[C(@"/tmp/") stringByDeletingLastPathComponent] isEqual: @""] && [[C(@"c:\\tmp/foo/") stringByDeletingLastPathComponent] isEqual: @"c:\\tmp"] && [[C(@"foo\\bar") stringByDeletingLastPathComponent] isEqual: @"foo"] && [[C(@"\\") stringByDeletingLastPathComponent] isEqual: @""] && [[C(@"foo") stringByDeletingLastPathComponent] isEqual: @"."]) -#endif +# endif # if !defined(OF_WINDOWS) && !defined(OF_MSDOS) # define EXPECTED @"/foo./bar" # else # define EXPECTED @"\\foo.\\bar"