Index: src/OFString+PathAdditions_DOS.m ================================================================== --- src/OFString+PathAdditions_DOS.m +++ src/OFString+PathAdditions_DOS.m @@ -33,24 +33,23 @@ for (OFString *component in components) { if (component.length == 0) continue; - if ([component isEqual: @"\\"] || [component isEqual: @"/"]) + if (!first && ![ret hasSuffix: @":"] && + ([component isEqual: @"\\"] || [component isEqual: @"/"])) continue; - if (!first && ![ret hasSuffix: @"\\"] && ![ret hasSuffix: @"/"]) + if (!first && ![ret hasSuffix: @"\\"] && + ![ret hasSuffix: @"/"] && ![ret hasSuffix: @":"]) [ret appendString: @"\\"]; [ret appendString: component]; first = false; } - if ([ret hasSuffix: @":"]) - [ret appendString: @"\\"]; - [ret makeImmutable]; objc_autoreleasePoolPop(pool); return ret; @@ -70,30 +69,46 @@ { OFMutableArray OF_GENERIC(OFString *) *ret = [OFMutableArray array]; void *pool = objc_autoreleasePoolPush(); const char *cString = self.UTF8String; size_t i, last = 0, cStringLength = self.UTF8StringLength; + bool isUNC = false; if (cStringLength == 0) { objc_autoreleasePoolPop(pool); return ret; } if ([self hasPrefix: @"\\\\"]) { + isUNC = true; [ret addObject: @"\\\\"]; cString += 2; cStringLength -= 2; } for (i = 0; i < cStringLength; i++) { if (cString[i] == '\\' || cString[i] == '/') { - if (i - last != 0) + if (i == 0) + [ret addObject: [OFString + stringWithUTF8String: cString + length: 1]]; + else if (i - last != 0) [ret addObject: [OFString stringWithUTF8String: cString + last length: i - last]]; + last = i + 1; + } else if (!isUNC && cString[i] == ':') { + if (i + 1 < cStringLength && + (cString[i + 1] == '\\' || cString[i + 1] == '/')) + i++; + + [ret addObject: [OFString + stringWithUTF8String: cString + last + length: i - last + 1]]; + last = i + 1; } } if (i - last != 0) [ret addObject: [OFString stringWithUTF8String: cString + last @@ -106,63 +121,25 @@ return ret; } - (OFString *)lastPathComponent { - void *pool = objc_autoreleasePoolPush(); - const char *cString; - size_t cStringLength; - ssize_t i; - OFString *ret; - - if ([self hasSuffix: @":\\"] || [self hasSuffix: @":/"]) - return self; - -#ifdef OF_WINDOWS - if ([self isEqual: @"\\\\"]) - return self; -#endif - - cString = self.UTF8String; - cStringLength = self.UTF8StringLength; - - if (cStringLength == 0) { - objc_autoreleasePoolPop(pool); - return @""; - } - - if (cString[cStringLength - 1] == '\\' || - cString[cStringLength - 1] == '/') - cStringLength--; - - if (cStringLength == 0) { - objc_autoreleasePoolPop(pool); - return @""; - } - - if (cStringLength - 1 > SSIZE_MAX) - @throw [OFOutOfRangeException exception]; - - for (i = cStringLength - 1; i >= 0; i--) { - if (cString[i] == '\\' || cString[i] == '/') { - i++; - break; - } - } - - /* - * Only one component, but the trailing delimiter might have been - * removed, so return a new string anyway. - */ - if (i < 0) - i = 0; - - ret = [[OFString alloc] initWithUTF8String: cString + i - length: cStringLength - i]; - - objc_autoreleasePoolPop(pool); - + /* + * Windows/DOS need the full parsing to determine the last path + * component. This could be optimized by not creating the temporary + * objects, though. + */ + void *pool = objc_autoreleasePoolPush(); + OFString *ret = self.pathComponents.lastObject; + + if (ret == nil) { + objc_autoreleasePoolPop(pool); + return @""; + } + + [ret retain]; + objc_autoreleasePoolPop(pool); return [ret autorelease]; } - (OFString *)pathExtension { @@ -186,75 +163,47 @@ return [ret autorelease]; } - (OFString *)stringByDeletingLastPathComponent { - void *pool = objc_autoreleasePoolPush(); - const char *cString; - size_t cStringLength; -#ifdef OF_WINDOWS - bool isUNC = false; -#endif - OFString *ret; - - if ([self hasSuffix: @":\\"] || [self hasSuffix: @":/"]) - return self; - - cString = self.UTF8String; - cStringLength = self.UTF8StringLength; - -#ifdef OF_WINDOWS - if ([self hasPrefix: @"\\\\"]) { - isUNC = true; - cString += 2; - cStringLength -= 2; - } -#endif - - if (cStringLength == 0) { - objc_autoreleasePoolPop(pool); - return self; - } - - if (cString[cStringLength - 1] == '\\' || - cString[cStringLength - 1] == '/') - cStringLength--; - - if (cStringLength == 0) { - objc_autoreleasePoolPop(pool); -#ifdef OF_WINDOWS - return (isUNC ? @"\\\\" : @""); -#else - return @""; -#endif - } - - for (size_t i = cStringLength; i >= 1; i--) { - if (cString[i - 1] == '\\' || cString[i - 1] == '/') { -#ifdef OF_WINDOWS - if (isUNC) { - cString -= 2; - i += 2; - } -#endif - - ret = [[OFString alloc] initWithUTF8String: cString - length: i - 1]; - - objc_autoreleasePoolPop(pool); - - return [ret autorelease]; - } - } - - objc_autoreleasePoolPop(pool); - -#ifdef OF_WINDOWS - return (isUNC ? @"\\\\" : @"."); -#else - return @"."; -#endif + /* + * Windows/DOS need the full parsing to delete the last path component. + * This could be optimized, though. + */ + void *pool = objc_autoreleasePoolPush(); + OFArray OF_GENERIC(OFString *) *components = self.pathComponents; + size_t count = components.count; + OFString *ret; + + if (count == 0) { + objc_autoreleasePoolPop(pool); + return @""; + } + + if (count == 1) { + OFString *firstComponent = components.firstObject; + + if ([firstComponent hasSuffix: @":"] || + [firstComponent hasSuffix: @":\\"] || + [firstComponent hasSuffix: @":/"] || + [firstComponent hasPrefix: @"\\"]) { + ret = [firstComponent retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; + } + + objc_autoreleasePoolPop(pool); + return @"."; + } + + components = [components objectsInRange: + of_range(0, components.count - 1)]; + ret = [OFString pathWithComponents: components]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; } - (OFString *)stringByDeletingPathExtension { void *pool; @@ -291,13 +240,10 @@ { void *pool = objc_autoreleasePoolPush(); OFArray OF_GENERIC(OFString *) *components; OFMutableArray OF_GENERIC(OFString *) *array; OFString *ret; -#ifdef OF_WINDOWS - bool isUNC = false; -#endif bool done = false; if (self.length == 0) return @""; @@ -308,17 +254,10 @@ return [[self copy] autorelease]; } array = [[components mutableCopy] autorelease]; -#ifdef OF_WINDOWS - if ([array.firstObject isEqual: @"\\\\"]) { - isUNC = true; - [array removeObjectAtIndex: 0]; - } -#endif - while (!done) { size_t length = array.count; done = true; @@ -333,36 +272,26 @@ done = false; break; } - if ([component isEqual: @".."] && - parent != nil && ![parent isEqual: @".."]) { + if ([component isEqual: @".."] && parent != nil && + ![parent isEqual: @".."] && + ![parent hasSuffix: @":"] && + ![parent hasSuffix: @":\\"] && + ![parent hasSuffix: @"://"] && + (![parent hasPrefix: @"\\"] || i != 1)) { [array removeObjectsInRange: of_range(i - 1, 2)]; done = false; break; } } } -#ifdef OF_WINDOWS - if (isUNC) { - /* - * Only one \ is needed, as the other one is added by - * -[componentsJoinedByString:]. - */ - [array insertObject: @"\\" - atIndex: 0]; - } -#endif - - if ([self hasSuffix: @"\\"] || [self hasSuffix: @"/"]) - [array addObject: @""]; - - ret = [[array componentsJoinedByString: @"\\"] retain]; + ret = [[OFString pathWithComponents: array] retain]; objc_autoreleasePoolPop(pool); return [ret autorelease]; } Index: tests/OFStringTests.m ================================================================== --- tests/OFStringTests.m +++ tests/OFStringTests.m @@ -643,36 +643,66 @@ #ifdef OF_HAVE_FILES # if defined(OF_WINDOWS) TEST(@"+[pathWithComponents:]", [[stringClass pathWithComponents: [OFArray arrayWithObjects: @"foo", @"bar", @"baz", nil]] isEqual: @"foo\\bar\\baz"] && + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"c:\\", @"foo", @"bar", @"baz", nil]] + isEqual: @"c:\\foo\\bar\\baz"] && [[stringClass pathWithComponents: [OFArray arrayWithObjects: @"c:", @"foo", @"bar", @"baz", nil]] + isEqual: @"c:foo\\bar\\baz"] && + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"c:", @"\\", @"foo", @"bar", @"baz", nil]] isEqual: @"c:\\foo\\bar\\baz"] && + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"c:", @"/", @"foo", @"bar", @"baz", nil]] + isEqual: @"c:/foo\\bar\\baz"] && [[stringClass pathWithComponents: [OFArray arrayWithObjects: @"foo/", @"bar\\", @"", @"baz", @"\\", nil]] isEqual: @"foo/bar\\baz"] && [[stringClass pathWithComponents: [OFArray arrayWithObjects: @"foo", nil]] isEqual: @"foo"] && [[stringClass pathWithComponents: [OFArray arrayWithObject: @"c:"]] - isEqual: @"c:\\"] && + isEqual: @"c:"] && + [[stringClass pathWithComponents: + [OFArray arrayWithObject: @"c:\\"]] isEqual: @"c:\\"] && + [[stringClass pathWithComponents: + [OFArray arrayWithObject: @"\\"]] isEqual: @"\\"] && + [[stringClass pathWithComponents: + [OFArray arrayWithObject: @"/"]] isEqual: @"/"] && [[stringClass pathWithComponents: [OFArray arrayWithObjects: @"\\\\", @"foo", @"bar", nil]] isEqual: @"\\\\foo\\bar"]) # elif defined(OF_MSDOS) TEST(@"+[pathWithComponents:]", [[stringClass pathWithComponents: [OFArray arrayWithObjects: @"foo", @"bar", @"baz", nil]] isEqual: @"foo\\bar\\baz"] && + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"c:\\", @"foo", @"bar", @"baz", nil]] + isEqual: @"c:\\foo\\bar\\baz"] && [[stringClass pathWithComponents: [OFArray arrayWithObjects: @"c:", @"foo", @"bar", @"baz", nil]] + isEqual: @"c:foo\\bar\\baz"] && + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"c:", @"\\", @"foo", @"bar", @"baz", nil]] isEqual: @"c:\\foo\\bar\\baz"] && + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"c:", @"/", @"foo", @"bar", @"baz", nil]] + isEqual: @"c:/foo\\bar\\baz"] && [[stringClass pathWithComponents: [OFArray arrayWithObjects: @"foo/", @"bar\\", @"", @"baz", @"\\", nil]] isEqual: @"foo/bar\\baz"] && [[stringClass pathWithComponents: [OFArray arrayWithObjects: @"foo", nil]] isEqual: @"foo"] && [[stringClass pathWithComponents: [OFArray arrayWithObject: @"c:"]] - isEqual: @"c:\\"]) + isEqual: @"c:"] && + [[stringClass pathWithComponents: + [OFArray arrayWithObject: @"c:\\"]] isEqual: @"c:\\"] && + [[stringClass pathWithComponents: + [OFArray arrayWithObject: @"\\"]] isEqual: @"\\"] && + [[stringClass pathWithComponents: + [OFArray arrayWithObject: @"/"]] isEqual: @"/"]) # elif defined(OF_AMIGAOS) TEST(@"+[pathWithComponents:]", [[stringClass pathWithComponents: [OFArray arrayWithObjects: @"dh0:", @"foo", @"bar", @"baz", nil]] isEqual: @"dh0:foo/bar/baz"] && @@ -712,18 +742,24 @@ # if defined(OF_WINDOWS) TEST(@"-[pathComponents]", /* c:/tmp */ (a = C(@"c:/tmp").pathComponents) && a.count == 2 && - [[a objectAtIndex: 0] isEqual: @"c:"] && + [[a objectAtIndex: 0] isEqual: @"c:/"] && [[a objectAtIndex: 1] isEqual: @"tmp"] && /* c:\tmp\ */ (a = C(@"c:\\tmp\\").pathComponents) && a.count == 2 && - [[a objectAtIndex: 0] isEqual: @"c:"] && + [[a objectAtIndex: 0] isEqual: @"c:\\"] && [[a objectAtIndex: 1] isEqual: @"tmp"] && + /* c:\ */ + (a = C(@"c:\\").pathComponents) && a.count == 1 && + [[a objectAtIndex: 0] isEqual: @"c:\\"] && /* c:/ */ (a = C(@"c:/").pathComponents) && a.count == 1 && + [[a objectAtIndex: 0] isEqual: @"c:/"] && + /* c: */ + (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"] && @@ -743,18 +779,24 @@ C(@"").pathComponents.count == 0) # elif defined(OF_MSDOS) TEST(@"-[pathComponents]", /* c:/tmp */ (a = C(@"c:/tmp").pathComponents) && a.count == 2 && - [[a objectAtIndex: 0] isEqual: @"c:"] && + [[a objectAtIndex: 0] isEqual: @"c:/"] && [[a objectAtIndex: 1] isEqual: @"tmp"] && /* c:\tmp\ */ (a = C(@"c:\\tmp\\").pathComponents) && a.count == 2 && - [[a objectAtIndex: 0] isEqual: @"c:"] && + [[a objectAtIndex: 0] isEqual: @"c:\\"] && [[a objectAtIndex: 1] isEqual: @"tmp"] && + /* c:\ */ + (a = C(@"c:\\").pathComponents) && a.count == 1 && + [[a objectAtIndex: 0] isEqual: @"c:\\"] && /* c:/ */ (a = C(@"c:/").pathComponents) && a.count == 1 && + [[a objectAtIndex: 0] isEqual: @"c:/"] && + /* c: */ + (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"] && @@ -849,11 +891,11 @@ TEST(@"-[lastPathComponent]", [C(@"c:/tmp").lastPathComponent isEqual: @"tmp"] && [C(@"c:\\tmp\\").lastPathComponent isEqual: @"tmp"] && [C(@"c:\\").lastPathComponent isEqual: @"c:\\"] && [C(@"c:/").lastPathComponent isEqual: @"c:/"] && - [C(@"\\").lastPathComponent isEqual: @""] && + [C(@"\\").lastPathComponent isEqual: @"\\"] && [C(@"foo").lastPathComponent isEqual: @"foo"] && [C(@"foo\\bar").lastPathComponent isEqual: @"bar"] && [C(@"foo/bar/baz/").lastPathComponent isEqual: @"baz"] && [C(@"\\\\foo\\bar").lastPathComponent isEqual: @"bar"] && [C(@"\\\\").lastPathComponent isEqual: @"\\\\"]) @@ -861,11 +903,11 @@ TEST(@"-[lastPathComponent]", [C(@"c:/tmp").lastPathComponent isEqual: @"tmp"] && [C(@"c:\\tmp\\").lastPathComponent isEqual: @"tmp"] && [C(@"c:\\").lastPathComponent isEqual: @"c:\\"] && [C(@"c:/").lastPathComponent isEqual: @"c:/"] && - [C(@"\\").lastPathComponent isEqual: @""] && + [C(@"\\").lastPathComponent isEqual: @"\\"] && [C(@"foo").lastPathComponent isEqual: @"foo"] && [C(@"foo\\bar").lastPathComponent isEqual: @"bar"] && [C(@"foo/bar/baz/").lastPathComponent isEqual: @"baz"]) # elif defined(OF_AMIGAOS) TEST(@"-[lastPathComponent]", @@ -901,36 +943,36 @@ [C(@"foo/.bar.baz").pathExtension isEqual: @"baz"] && [C(@"foo/bar.baz/").pathExtension isEqual: @"baz"]) # if defined(OF_WINDOWS) TEST(@"-[stringByDeletingLastPathComponent]", - [C(@"\\tmp").stringByDeletingLastPathComponent isEqual: @""] && - [C(@"/tmp/").stringByDeletingLastPathComponent isEqual: @""] && + [C(@"\\tmp").stringByDeletingLastPathComponent isEqual: @"\\"] && + [C(@"/tmp/").stringByDeletingLastPathComponent isEqual: @"/"] && [C(@"c:\\").stringByDeletingLastPathComponent isEqual: @"c:\\"] && [C(@"c:/").stringByDeletingLastPathComponent isEqual: @"c:/"] && [C(@"c:\\tmp/foo/").stringByDeletingLastPathComponent isEqual: @"c:\\tmp"] && [C(@"foo\\bar").stringByDeletingLastPathComponent isEqual: @"foo"] && - [C(@"\\").stringByDeletingLastPathComponent isEqual: @""] && + [C(@"\\").stringByDeletingLastPathComponent isEqual: @"\\"] && [C(@"foo").stringByDeletingLastPathComponent isEqual: @"."] && [C(@"\\\\foo\\bar").stringByDeletingLastPathComponent isEqual: @"\\\\foo"] && [C(@"\\\\foo").stringByDeletingLastPathComponent isEqual: @"\\\\"] && [C(@"\\\\").stringByDeletingLastPathComponent isEqual: @"\\\\"]) # elif defined(OF_MSDOS) TEST(@"-[stringByDeletingLastPathComponent]", - [C(@"\\tmp").stringByDeletingLastPathComponent isEqual: @""] && - [C(@"/tmp/").stringByDeletingLastPathComponent isEqual: @""] && + [C(@"\\tmp").stringByDeletingLastPathComponent isEqual: @"\\"] && + [C(@"/tmp/").stringByDeletingLastPathComponent isEqual: @"/"] && [C(@"c:\\").stringByDeletingLastPathComponent isEqual: @"c:\\"] && [C(@"c:/").stringByDeletingLastPathComponent isEqual: @"c:/"] && [C(@"c:\\tmp/foo/").stringByDeletingLastPathComponent isEqual: @"c:\\tmp"] && [C(@"foo\\bar").stringByDeletingLastPathComponent isEqual: @"foo"] && - [C(@"\\").stringByDeletingLastPathComponent isEqual: @""] && + [C(@"\\").stringByDeletingLastPathComponent isEqual: @"\\"] && [C(@"foo").stringByDeletingLastPathComponent isEqual: @"."]) # elif defined(OF_AMIGAOS) TEST(@"-[stringByDeletingLastPathComponent]", [C(@"dh0:").stringByDeletingLastPathComponent isEqual: @"dh0:"] && [C(@"dh0:tmp").stringByDeletingLastPathComponent @@ -1017,11 +1059,13 @@ # ifdef OF_WINDOWS /* TODO: Add more tests */ TEST(@"-[stringByStandardizingPath]", [C(@"\\\\foo\\..\\bar\\qux").stringByStandardizingPath - isEqual: @"\\\\bar\\qux"]) + isEqual: @"\\\\bar\\qux"] && + [C(@"c:\\..\\asd").stringByStandardizingPath + isEqual: @"c:\\..\\asd"]) # endif #endif TEST(@"-[decimalValue]", C(@"1234").decimalValue == 1234 &&