NSString unique file path to avoid name collisions - objective-c

Is there a simple way to take a given file path and modify it in order to avoid name collisions? Something like:
[StringUtils stringToAvoidNameCollisionForPath:path];
that for a given path of type: /foo/bar/file.png, will return /foo/bar/file-1.png and later it will increment that "-1" similarly to what Safari does for downloaded files.
UPDATE:
I followed Ash Furrow's suggestion and I posted my implementation as answer :)

I had a similar problem, and came up with a slightly broader approach, that attempts to name files the same way iTunes would (when you have it set to manage your library and you have multiple tracks with the same name, etc.)
It works in a loop, so the function can be called multiple times and still produce valid output. Explaining the arguments, fileName is the name of the file with no path or extension (e.g. "file"), folder is just the path (e.g. "/foo/bar"), and fileType is just the extension (e.g. "png"). These three could be passed in as one string and be split out after, but in my case it made sense to separate them.
currentPath (which can be empty, but not nil), is useful when you're renaming a file, not creating a new one. For example, if you have "/foo/bar/file 1.png" that you're trying to rename to "/foo/bar/file.png", you would pass in "/foo/bar/file 1.png" for currentPath, and if "/foo/bar/file.png" already exists, you'll get back the path you started with, instead of seeing that "/foo/bar/file 1.png" and returning "/foo/bar/file 2.png"
+ (NSString *)uniqueFile:(NSString *)fileName
inFolder:(NSString *)folder
withExtension:(NSString *)fileType
mayDuplicatePath:(NSString *)currentPath
{
NSUInteger existingCount = 0;
NSString *result;
NSFileManager *manager = [NSFileManager defaultManager];
do {
NSString *format = existingCount > 0 ? #"%# %lu" : #"%#";
fileName = [NSString stringWithFormat:format, fileName, existingCount++];
result = [fileName stringByAppendingFormat:#".%#", [fileType lowercaseString]];
result = [folder stringByAppendingPathComponent:result];
} while ([manager fileExistsAtPath:result] &&
// This comparison must be case insensitive, as the file system is most likely so
[result caseInsensitiveCompare:currentPath] != NSOrderedSame);
return result;
}

I decided to implement my own solution and I want to share my code. It's not the most desirable implementation, but it seems to do the job:
+ (NSString *)stringToAvoidNameCollisionForPath:(NSString *)path {
// raise an exception for invalid paths
if (path == nil || [path length] == 0) {
[NSException raise:#"DMStringUtilsException" format:#"Invalid path"];
}
NSFileManager *manager = [[[NSFileManager alloc] init] autorelease];
BOOL isDirectory;
// file does not exist, so the path doesn't need to change
if (![manager fileExistsAtPath:path isDirectory:&isDirectory]) {
return path;
}
NSString *lastComponent = [path lastPathComponent];
NSString *fileName = isDirectory ? lastComponent : [lastComponent stringByDeletingPathExtension];
NSString *ext = isDirectory ? #"" : [NSString stringWithFormat:#".%#", [path pathExtension]];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"-([0-9]{1,})$" options:0 error:nil];
NSArray *matches = [regex matchesInString:fileName options:0 range:STRING_RANGE(fileName)];
// missing suffix... start from 1 (foo-1.ext)
if ([matches count] == 0) {
return [NSString stringWithFormat:#"%#-1%#", fileName, ext];
}
// get last match (theoretically the only one due to "$" in the regex)
NSTextCheckingResult *result = (NSTextCheckingResult *)[matches lastObject];
// extract suffix value
NSUInteger counterValue = [[fileName substringWithRange:[result rangeAtIndex:1]] integerValue];
// remove old suffix from the string
NSString *fileNameNoSuffix = [fileName stringByReplacingCharactersInRange:[result rangeAtIndex:0] withString:#""];
// return the path with the incremented counter suffix
return [NSString stringWithFormat:#"%#-%i%#", fileNameNoSuffix, counterValue + 1, ext];
}
... and the following are the tests I used:
- (void)testStringToAvoidNameCollisionForPath {
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
// bad configs //
STAssertThrows([DMStringUtils stringToAvoidNameCollisionForPath:nil], nil);
STAssertThrows([DMStringUtils stringToAvoidNameCollisionForPath:#""], nil);
// files //
NSString *path = [bundle pathForResource:#"bar-0.abc" ofType:#"txt"];
NSString *savePath = [DMStringUtils stringToAvoidNameCollisionForPath:path];
STAssertEqualObjects([savePath lastPathComponent], #"bar-0.abc-1.txt", nil);
NSString *path1 = [bundle pathForResource:#"bar1" ofType:#"txt"];
NSString *savePath1 = [DMStringUtils stringToAvoidNameCollisionForPath:path1];
STAssertEqualObjects([savePath1 lastPathComponent], #"bar1-1.txt", nil);
NSString *path2 = [bundle pathForResource:#"bar51.foo.yeah1" ofType:#"txt"];
NSString *savePath2 = [DMStringUtils stringToAvoidNameCollisionForPath:path2];
STAssertEqualObjects([savePath2 lastPathComponent], #"bar51.foo.yeah1-1.txt", nil);
NSString *path3 = [path1 stringByDeletingLastPathComponent];
NSString *savePath3 = [DMStringUtils stringToAvoidNameCollisionForPath:[path3 stringByAppendingPathComponent:#"xxx.zip"]];
STAssertEqualObjects([savePath3 lastPathComponent], #"xxx.zip", nil);
NSString *path4 = [bundle pathForResource:#"foo.bar1-1-2-3-4" ofType:#"txt"];
NSString *savePath4 = [DMStringUtils stringToAvoidNameCollisionForPath:path4];
STAssertEqualObjects([savePath4 lastPathComponent], #"foo.bar1-1-2-3-5.txt", nil);
NSString *path5 = [bundle pathForResource:#"bar1-1" ofType:#"txt"];
NSString *savePath5 = [DMStringUtils stringToAvoidNameCollisionForPath:path5];
STAssertEqualObjects([savePath5 lastPathComponent], #"bar1-2.txt", nil);
// folders //
NSString *path6 = [DOCUMENTS_PATH stringByAppendingPathComponent:#"foo1"];
NSString *savePath6 = [DMStringUtils stringToAvoidNameCollisionForPath:path6];
STAssertEqualObjects([savePath6 lastPathComponent], #"foo1-1", nil);
NSString *path7 = [DOCUMENTS_PATH stringByAppendingPathComponent:#"bar1-1"];
NSString *savePath7 = [DMStringUtils stringToAvoidNameCollisionForPath:path7];
STAssertEqualObjects([savePath7 lastPathComponent], #"bar1-2", nil);
NSString *path8 = [DOCUMENTS_PATH stringByAppendingPathComponent:#"foo-5.bar123"];
NSString *savePath8 = [DMStringUtils stringToAvoidNameCollisionForPath:path8];
STAssertEqualObjects([savePath8 lastPathComponent], #"foo-5.bar123-1", nil);
}

Related

Cannot append to NSString

I'm trying to append the file extension to a the stringValue returned by a subclassed NSTextFieldCell
I've tried everything I knew and could find on the internet, but this is just giving me a headache
the method is the following:
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSMutableString *filename = [[NSMutableString alloc] init];
[filename appendString:self.stringValue];
NSString *iconFileName = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:filename] stringByAppendingPathExtension:#"png"];
NSLog(#"%#", iconFileName);
}
The returned value is without the extension though!
I've also tried the following:
filename = [NSString stringWithFormat: #"%#.png", filename];
This returns the "filename" string without the ".png"
Similarly:
filename = [filename stringByAppendingString: #".png"];
returns just the "filename"
The table column where this cell belongs to is bound to an NSObject, and the method that sends the data to the column is the following:
- (NSString *) nationString {
NSMutableString *string = [[NSMutableString alloc] init];
int index = 0;
if (nationAddress && nationAddress > 0x0) {
index = [[[[controller database] nationsAddressIndex] valueForKey:[NSString stringWithFormat:#"%lli", nationAddress]] intValue];
Nation *nationality = [[[controller database] nations] objectAtIndex:index];
[string appendString:[nationality name]];
}
else {
[string appendString:#"---"];
}
return string;
}
Anyone has any idea why this might be happening, or can suggest any alternatives?
Any help will be appreciated
Thanks
This should return the complete path with extension:
NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Default.png"];
NSLog(#"%#", path);
So, assuming self.stringValue includes the extension, your method should work with this:
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSString *iconFileName = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:self.stringValue];
NSLog(#"%#", iconFileName);
}
If it doesn't include the extension, try this:
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSString *strWithPath = [NSString stringWithFormat:#"%#.png", self.stringValue];
NSString *iconFileName = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:strWithPath];
NSLog(#"%#", iconFileName);
}
Just for test. Try to use this code and update here a output values:
NSMutableString *filename = [[NSMutableString alloc] init];
[filename appendString:self.stringValue];
NSLog(#"text1: %# ;", filename);
filename = [NSString stringWithFormat: #"%#.png", filename];
filename = [NSString stringWithFormat: #"%#.png%#", filename, filename];
NSLog(#"text2: %# ;", filename);
These should work (barring a typo):
NSString* filename = #"abc";
NSString* result1 = [NSString stringWithFormat:#"%#.png", filename];
NSString* result2 = [filename stringByAppendingString:#".png"];
NSMutableString* result3 = [filename mutableCopy];
[result3 appendString:#".png"];
If they don't appear to be working then you have some problem with how you're initializing or displaying your values.
Hint: Place an NSLog(#"The answer is %#", resultN); statement immediately after each of the above (with "resultN" changed appropriately) to see what you're getting. Keep in mind that if you look from a different object you may be looking at different variables.

Inputstring is null after being set in a static method, why?

I am uncertain of how memory is managed in my particular case...
I have two methods:
+(NSMutableDictionary *)loadPlist: (NSString*) name
andErrorDesc: (NSString*) errorDesc
andFormat: (NSPropertyListFormat*) format
andplistPath: (NSMutableString*) plistPath
{
NSString * destPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
destPath = [destPath stringByAppendingPathComponent:
[NSString stringWithFormat:#"%#.plist", name]];
if (![[NSFileManager defaultManager] fileExistsAtPath:destPath])
{
[[NSFileManager defaultManager] copyItemAtPath:[[NSBundle mainBundle] pathForResource:name ofType:#"plist"] toPath:destPath error:nil];
}
plistPath = [NSMutableString stringWithString:[destPath copy]];
NSData * plistXML =
[[NSFileManager defaultManager] contentsAtPath:plistPath];
NSLog(#"AFTER plistPath: \n%#",plistPath);
return
(NSMutableDictionary *)[NSPropertyListSerialization
propertyListFromData:plistXML
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:format
errorDescription:&errorDesc];
}
+(bool)writeToCache:(NSString*) data andField: (NSString*) field
{
NSString * errorDesc = nil;
NSPropertyListFormat format;
NSMutableString * plistPath;
NSMutableDictionary * temp = [BPUtils loadPlist:#"cache" andErrorDesc:errorDesc andFormat:&format andplistPath:plistPath];
if (!temp)
{
NSLog(#"Error reading plist: %#, format: %d", errorDesc, format);
return false;
}
NSMutableArray * arr = [temp objectForKey:field];
[arr addObject:data];
NSLog(#"path: %#",plistPath);
// Write to plist
bool res = [temp writeToFile:plistPath atomically:YES];
NSLog(#"RES: %d", res);
return true;
}
The problem is that the bottom method that sends in "plistPath" to the above method retrives a null plistPath after the above method has set it. Why and how can I fix this?
NSLog(#"path: %#",plistPath);
in the bottom method shows null, why?
I use ARC. Also "destPath" is set and shows the correct path.
I believe you could be a bit confused here.
You are creating plistPath in the bottom method. And then you pass plistPath into
[BPUtils loadPlist:#"cache" andErrorDesc:errorDesc andFormat:&format andplistPath:plistPath];
but plistPath is NULL
NSMutableString * plistPath; // Is NULL
But once it has been passed in the local plistPath takes over.
+(NSMutableDictionary *)loadPlist: (NSString*) name
andErrorDesc: (NSString*) errorDesc
andFormat: (NSPropertyListFormat*) format
andplistPath: (NSMutableString*) plistPath // Notice the local plistPath variable. This is the one you are playing with in this method.
At this point you are setting plistPath but remember it is still just a local variable and not an instance variable. So the button method will never know about it being set, as far as the button method is concerned it is still NULL
plistPath = [NSMutableString stringWithString:[destPath copy]];
So whatever you set in plistPath in the top method will not get passed back to the bottom method, think of the top plistPath as being deallocated when the method does the return.
So the plistPath in the bottom method will remain NULL
So try this instead SOLUTION
static NSMutableString *yourNewStringforPlistPath; //This will be NULL
+(NSMutableDictionary *)loadPlist: (NSString*) name
andErrorDesc: (NSString*) errorDesc
andFormat: (NSPropertyListFormat*) format
andplistPath: (NSMutableString*) plistPath
{
NSString * destPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
destPath = [destPath stringByAppendingPathComponent:
[NSString stringWithFormat:#"%#.plist", name]];
if (![[NSFileManager defaultManager] fileExistsAtPath:destPath])
{
[[NSFileManager defaultManager] copyItemAtPath:[[NSBundle mainBundle] pathForResource:name ofType:#"plist"] toPath:destPath error:nil];
}
yourNewStringforPlistPath = [NSMutableString stringWithString:[destPath copy]];
NSData * plistXML =
[[NSFileManager defaultManager] contentsAtPath:yourNewStringforPlistPath];
NSLog(#"AFTER plistPath: \n%#",yourNewStringforPlistPath);
return
(NSMutableDictionary *)[NSPropertyListSerialization
propertyListFromData:plistXML
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:format
errorDescription:&errorDesc];
}
+(bool)writeToCache:(NSString*) data andField: (NSString*) field
{
NSString * errorDesc = nil;
NSPropertyListFormat format;
NSMutableDictionary * temp = [BPUtils loadPlist:#"cache" andErrorDesc:errorDesc andFormat:&format andplistPath:[NSNull null]]; // As this is already NULL you don't really need to pass yourNewStringforPlistPath in unless in the future this value can be set before this.
if (!temp)
{
NSLog(#"Error reading plist: %#, format: %d", errorDesc, format);
return false;
}
NSMutableArray * arr = [temp objectForKey:field];
[arr addObject:data];
NSLog(#"path: %#",yourNewStringforPlistPath);
// Write to plist
bool res = [temp writeToFile:yourNewStringforPlistPath atomically:YES];
NSLog(#"RES: %d", res);
return true;
}

Mysterious Memory Leak - NSString Autorelease Issue

Even though I'm using NSAutoreleasePool in a tight loop, the following line in the method below is causing me to get memory warnings and ultimately crashing my app (by commenting out that line, the problem goes away). Anyone have an idea why this is the case?
filepath = [docpath stringByAppendingPathComponent:file];
-(void)fileCleanup
{
NSString *documentspath = [AppSession documentsDirectory];
NSString *docpath = [documentspath stringByAppendingPathComponent:#"docs"];
NSFileManager *filemanager = [NSFileManager defaultManager];
NSArray *files = [filemanager subpathsOfDirectoryAtPath:docpath error:NULL];
NSLog(#"fileCleanup");
if(files != nil && [files count] > 0)
{
BOOL deletefile;
NSString *filepath;
NSAutoreleasePool *readPool;
NSString *testfile;
NSString *file;
for(file in files)
{
deletefile = YES;
for (testfile in allFiles) {
readPool = [[NSAutoreleasePool alloc] init];
//line below is causing memory leak!
filepath = [docpath stringByAppendingPathComponent:file];
//if([filepath isEqualToString:testfile])
//{
// deletefile = NO;
// break;
//}
[readPool drain];
}
if(deletefile)
{
[self logText:[#"\nD: " stringByAppendingString:[#"docs/" stringByAppendingPathComponent:file]]];
[filemanager removeItemAtPath:[docpath stringByAppendingPathComponent:file] error:NULL];
}
}
}
I replaced the inner "for" loop with the Objective C equivalent of the php in_array() function and the memory issues disappeared!
NSUInteger matchint = [allFiles indexOfObject:[docpath stringByAppendingPathComponent:file]];
if(matchint != NSNotFound)
deletefile = NO;
The break statement was dropping you out of your inner for loop without calling -drain on the NSAutoreleasePool.

Check the attribute of items in a directory in Objective-C

I have made this little code to check how many subdirectories are in a given directory. It checks only the first level, is there anyway I can make it simplier? I have added comments, maybe easier to understand my intention. Thank you!
#import < Foundation/Foundation.h >
int main (int argc, const char * argv[]){
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// insert code here...
NSFileManager *filemgr;
NSMutableArray *listOfFiles;
NSDictionary *listOfFolders;
NSDictionary *controllDir;
int i, count;
NSString *myPath;
filemgr = [NSFileManager defaultManager];
myPath = #"/";
// list the files in the given directory (myPath)
listOfFiles = [filemgr directoryContentsAtPath: myPath];
// count the number of elements in the array
count = [listOfFiles count];
// check them one by one
for (i = 0; i < count; i++)
{
// I need the full path
NSString *filePath =[NSString stringWithFormat:#"%#/%#", myPath, [listOfFiles objectAtIndex: i]];
// add every item with its attributes
listOfFolders = [filemgr attributesOfItemAtPath:filePath error:NULL];
// to avoid typo get the attribute and create a string
controllDir = [filemgr attributesOfItemAtPath:#"/" error:NULL];
NSString *toCheck = [NSString stringWithFormat:#"%#", [controllDir objectForKey:NSFileType]];
// the folder elements one by one
NSString *fileType = [NSString stringWithFormat:#"%#", [listOfFolders objectForKey:NSFileType]];
if([toCheck isEqualToString:fileType])
{
NSLog(#"NAME: %# TYPE: %#" ,[listOfFiles objectAtIndex:i],[listOfFolders objectForKey:NSFileType]);
}
}
[pool drain];
return 0;
}
NSURL *url = [NSURL fileURLWithPath:#"/Users"];
NSError *error;
NSArray *items = [[NSFileManager defaultManager]
contentsOfDirectoryAtURL:url
includingPropertiesForKeys:[NSArray array]
options:0
error:&error];
NSMutableArray *dirs = [NSMutableArray array];
for (NSURL *url in items) {
if (CFURLHasDirectoryPath((CFURLRef)url)) {
[dirs addObject:url];
}
}
}
You can get fancy with blocks this way:
NSPredicate *predicate = [NSPredicate predicateWithBlock:
^BOOL (id evaluatedObject, NSDictionary *bindings){
return CFURLHasDirectoryPath((CFURLRef)evaluatedObject); }];
NSArray *dirs = [items filteredArrayUsingPredicate:predicate]);
Of note here is that it only hits the disk the one time, and it doesn't spend any time fetching unneeded attributes. Once you construct the NSURL, you can always tell if it's a directory because it ends in a / (this is specified behavior). That's all CFURLHasDirectoryPath() is doing. It doesn't actually hit the disk.
Brief thoughts (posting from a cell phone):
use an NSDirectoryEnumerator.
it has a method called fileAttributes that will return an NSDictionary with the item's attributes.
NSDictionary has a fileType method that will return a constant to indicate the kind of the item.
there's a nice constant called NSFileTypeDirectory you can use for comparison.
How's this?
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error;
NSArray *subpaths = [fm contentsOfDirectoryAtPath:myPath error:&error];
if (!subpaths) ...handle error.
NSMutableArray *subdirs = [NSMutableArray array];
for (NSString *name in subpaths)
{
NSString *subpath = [myPath stringByAppendingPathComponent:name];
BOOL isDir;
if ([fm fileExistsAtPath:subpath isDirectory:&isDir] &&
isDir)
{
[subdirs addObject:subpath];
}
}
Now the subdirs array contains all of the immediate subdirectories.

Url minus query string in Objective-C

What's the best way to get an url minus its query string in Objective-C? An example:
Input:
http://www.example.com/folder/page.htm?param1=value1&param2=value2
Output:
http://www.example.com/folder/page.htm
Is there a NSURL method to do this that I'm missing?
Since iOS 8/OS X 10.9, there is an easier way to do this with NSURLComponents.
NSURL *url = [NSURL URLWithString:#"http://hostname.com/path?key=value"];
NSURLComponents *urlComponents = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO];
urlComponents.query = nil; // Strip out query parameters.
NSLog(#"Result: %#", urlComponents.string); // Should print http://hostname.com/path
There's no NSURL method I can see. You might try something like:
NSURL *newURL = [[NSURL alloc] initWithScheme:[url scheme]
host:[url host]
path:[url path]];
Testing looks good:
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
NSAutoreleasePool *arp = [[NSAutoreleasePool alloc] init];
NSURL *url = [NSURL URLWithString:#"http://www.abc.com/foo/bar.cgi?a=1&b=2"];
NSURL *newURL = [[[NSURL alloc] initWithScheme:[url scheme]
host:[url host]
path:[url path]] autorelease];
NSLog(#"\n%# --> %#", url, newURL);
[arp release];
return 0;
}
Running this produces:
$ gcc -lobjc -framework Foundation -std=c99 test.m ; ./a.out
2010-11-25 09:20:32.189 a.out[36068:903]
http://www.abc.com/foo/bar.cgi?a=1&b=2 --> http://www.abc.com/foo/bar.cgi
Here is the Swift version of Andree's answer, with some extra flavour -
extension NSURL {
func absoluteStringByTrimmingQuery() -> String? {
if var urlcomponents = NSURLComponents(URL: self, resolvingAgainstBaseURL: false) {
urlcomponents.query = nil
return urlcomponents.string
}
return nil
}
}
You can call it like -
let urlMinusQueryString = url.absoluteStringByTrimmingQuery()
Swift Version
extension URL {
func absoluteStringByTrimmingQuery() -> String? {
if var urlcomponents = URLComponents(url: self, resolvingAgainstBaseURL: false) {
urlcomponents.query = nil
return urlcomponents.string
}
return nil
}
}
Hope this helps!
What you probably need is a combination of url's host and path components:
NSString *result = [[url host] stringByAppendingPathComponent:[url path]];
You could try using query of NSURL to get the parameters, then strip that value using stringByReplacingOccurrencesOfString of NSString?
NSURL *before = [NSURL URLWithString:#"http://www.example.com/folder/page.htm?param1=value1&param2=value2"];
NSString *after = [before.absoluteString stringByReplacingOccurrencesOfString:before.query withString:#""];
Note, the final URL will still end with ?, but you could easily strip that as well if needed.
I think -baseURL might do what you want.
If not, you can can do a round trip through NSString like so:
NSString *string = [myURL absoluteString];
NSString base = [[string componentsSeparatedByString:#"?"] objectAtIndex:0];
NSURL *trimmed = [NSURL URLWithString:base];
NSURL has a query property which contains everything after the ? in a GET url. So simply subtract that from the end of the absoluteString, and you've got the url without the query.
NSURL *originalURL = [NSURL URLWithString:#"https://winker#127.0.0.1:1000/file/path/?q=dogfood"];
NSString *strippedString = [originalURL absoluteString];
NSUInteger queryLength = [[originalURL query] length];
strippedString = (queryLength ? [strippedString substringToIndex:[strippedString length] - (queryLength + 1)] : strippedString);
NSLog(#"Output: %#", strippedString);
Logs:
Output: https://winker#127.0.0.1:1000/file/path/
The +1 is for the ? which is not part of query.
You might fancy the method replaceOccurrencesOfString:withString:options:range: of the NSMutableString class. I solved this by writing a category for NSURL:
#import <Foundation/Foundation.h>
#interface NSURL (StripQuery)
// Returns a new URL with the query stripped out.
// Note: If there is no query, returns a copy of this URL.
- (NSURL *)URLByStrippingQuery;
#end
#implementation NSURL (StripQuery)
- (NSURL *)URLByStrippingQuery
{
NSString *query = [self query];
// Simply copy if there was no query. (query is nil if URL has no '?',
// and equal to #"" if it has a '?' but no query after.)
if (!query || ![query length]) {
return [self copy];
}
NSMutableString *urlString = [NSMutableString stringWithString:[self absoluteString]];
[urlString replaceOccurrencesOfString:query
withString:#""
options:NSBackwardsSearch
range:NSMakeRange(0, [urlString length])];
return [NSURL URLWithString:urlString];
}
#end
This way, I can send this message to existing NSURL objects and have a new NSURL object be returned to me.
I tested it using this code:
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSURL *url = [NSURL URLWithString:#"http://www.example.com/script.php?key1=val1&key2=val2"];
// NSURL *url = [NSURL URLWithString:#"http://www.example.com/script.php?"];
// NSURL *url = [NSURL URLWithString:#"http://www.example.com/script.php"];
NSURL *newURL = [url URLByStrippingQuery];
NSLog(#"Original URL: \"%#\"\n", [url absoluteString]);
NSLog(#"Stripped URL: \"%#\"\n", [newURL absoluteString]);
}
return 0;
}
and I got the following output (minus the time stamps):
Original URL: "http://www.example.com/script.php?key1=val1&key2=val2"
Stripped URL: "http://www.example.com/script.php?"
Note that the question mark ('?') still remains. I will leave it up to the reader to remove it in a secure way.
We should try to use NSURLComponents
NSURL *url = #"http://example.com/test";
NSURLComponents *comps = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:YES];
NSString *cleanUrl = [NSString stringWithFormat:#"%#://%#",comps.scheme,comps.host];
if(comps.path.length > 0){
cleanUrl = [NSString stringWithFormat:#"%#/%#",cleanUrl,comps.path];
}
I think what you're looking for is baseUrl.