Reputation: 32163
I have an [URL]
which represent a set of special parent directories. I am given another [URL]
which represents files scattered around the system. I want to know if any of these files are in any of my special directories, or any of their subdirectories. Is there a simple/intended way to do this, without manually parsing/traversing an absolute URL's path?
Upvotes: 4
Views: 1848
Reputation: 3035
A slightly improved, Swift 5 version combining nice features of both Umair and rmaddy's existing excellent answers.
Like Umair's answer, this correctly handles the case where a folder in the path is a partial match (that is, it will not say that /foo/bar-baz/bang
has /foo/bar/
as an ancestor).
It also performs the canonicalization (standardizing paths to eliminate things like .
or ../
, and resolving symlinks) like rmaddy's answer.
func pathHasAncestor(maybeChild: URL, maybeAncestor: URL) -> Bool {
let ancestorComponents: [String] = canonicalize(maybeAncestor).pathComponents
let childComponents: [String] = canonicalize(maybeChild).pathComponents
return ancestorComponents.count < childComponents.count
&& !zip(ancestorComponents, childComponents).contains(where: !=)
}
func canonicalize(_ url: URL) -> URL {
url.standardizedFileURL.resolvingSymlinksInPath()
}
Here's a very simple unit test to verify the basic functionality:
import XCTest
class FileUtilsTests: XCTestCase {
func testPathHasAncestor() throws {
let leaf = URL(fileURLWithPath: "/foo/bar/baz")
let parent = leaf.deletingLastPathComponent()
XCTAssertTrue(pathHasAncestor(maybeChild: leaf, maybeAncestor: parent))
XCTAssertFalse(pathHasAncestor(maybeChild: URL(fileURLWithPath: "/foo/bar 1/baz"), maybeAncestor: parent))
XCTAssertFalse(pathHasAncestor(maybeChild: leaf, maybeAncestor: leaf))
}
}
Upvotes: 6
Reputation: 1253
The problem with the prefix solution is that it would fail in cases: eg.
PathA: /Documents/untitled folder (1)/SomeFolder/Xyz/Waterfall.mp3
PathB: /Documents/untitled folder
The prefix solution will tell you that the PathA is child of PathB, although that's not the case.
A manually traversing solution:
extension String {
func isChildPath(of path: String) -> Bool {
self.count < path.count
&& !zip(URL(fileURLWithPath: self).pathComponents,
URL(fileURLWithPath: path).pathComponents)
.contains(where: !=)
}
}
@interface NSString (Additions)
-(BOOL)isChildOfPath:(NSString *)path;
@end
@implementation NSString (Additions)
-(BOOL)isChildOfPath:(NSString *)path {
NSString * child = self;
NSString * parent = path;
NSArray<NSString *> * childPathComponents = child.pathComponents;
NSArray<NSString *> * parentPathComponents = parent.pathComponents;
if (parentPathComponents.count >= childPathComponents.count) {
return NO;
}
for (NSInteger i = 0; i < parentPathComponents.count; i++) {
NSString * parentComponent = parentPathComponents[i];
NSString * childComponent = childPathComponents[i];
if ( ![parentComponent isEqualToString:childComponent] ) {
return NO;
}
}
return YES;
}
@end
Upvotes: 2
Reputation: 318944
There's no method in NSURL
that lets you see if another NSURL
represents the root path of the other.
One possible solution is to convert the two URLs to path strings using the path
property. Then see if one string is the prefix of the other. But before getting the URL's path, use both URLByStandardizingPath
and URLByResolvingSymlinksInPath
to ensure a consistent result.
Example:
NSURL *specialURL = ... // a URL for one of the special parent directories
NSURL *fileURL = ... // a URL for one of the files to check
NSString *specialPath = [specialURL.URLByStandardizingPath.URLByResolvingSymlinksInPath.path stringByAppendingString:@"/"];
NSString *filePath = fileURL.URLByStandardizingPath.URLByResolvingSymlinksInPath.path
if ([filePath hasPrefix:specialPath]) {
// This file is in this special directory
}
Upvotes: 6