Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

Childs

macrumors member
Original poster
May 28, 2010
48
4
Hello,

Basically I'm doing something in NSTask, and I need to parse the output for something specific. The output of the NSTask would look something like this:

Code:
2010-05-28 18:28:22.256 someapp[91359:903] owner is: someuser
2010-05-28 18:28:22.300 someapp[91359:903] <appID01 B6977A4E-782D-48AA-8F7D-395388493F4E /> <appID02 2EF30775-0F9D-465F-B012-283C695CB668 />

What I'm looking to do is grab <appID02 2EF30775-0F9D-465F-B012-283C695CB668 />

I would presume there must be way to get a string from @"<appID02" to @"/>", but my eyeballs are about to explode looking for it. Any pointers?
 
rangeOfString: and/or rangeOfString:eek:ptions:range: should get you the positions of your substrings. From the end of the first range + 1 to start of the second - 1 should be your "payload". Once you know that a little substringWithRange: and voilà!

-Lee

edit: or start from the start of the first and go to the end of the second if you want those substrings included.
 
Probably a better solution would be to use an NSScanner to do this work. It is more difficult to use, but will better cope with input that might change slightly.
 
I assume that your input lines would always have this format?

Using NSScanner:
Code:
#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	
	NSString *input = @"<appID01 B6977A4E-782D-48AA-8F7D-395388493F4E />";
	NSScanner *scanner = [NSScanner scannerWithString: input];
	NSString *output;
	[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString: &output];
	NSLog(@"%@", output);  // output will have <appID01 in it
	[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString: &output];
	NSLog(@"%@", output); // output will have the longer token now
	
    [pool drain];
    return 0;
}
And taking advantage of the same space separated "list" you could use NSArrays too...

Code:
#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	
	NSString *input = @"<appID01 B6977A4E-782D-48AA-8F7D-395388493F4E />";
	
	NSArray *tokens = [input componentsSeparatedByString:@" "];
	if ([tokens count] > 1) {
		NSLog(@"%@", [tokens objectAtIndex:1]);
	}
	
    [pool drain];
    return 0;
}

EDIT: I just reread your original problem... a modification like this is probably in order.
Code:
#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	
	NSString *input = @"<appID01 B6977A4E-782D-48AA-8F7D-395388493F4E /> <appID02 2EF30775-0F9D-465F-B012-283C695CB668 />";
	
	NSArray *tokens = [input componentsSeparatedByString:@" "];
	NSMutableArray *strings = nil;
	if ([tokens count] % 3 == 0) {
		strings = [NSMutableArray arrayWithCapacity:[tokens count]/3];
		for (int i = 0; i < [tokens count]/3; i++){
			[strings addObject:[NSString stringWithFormat:@"%@ %@ %@", 
								[tokens objectAtIndex:(i*3)],
								[tokens objectAtIndex:(i*3)+1],
								[tokens objectAtIndex:(i*3)+2]]];
		}
	}
	
	for (int i = 0; i < [strings count]; i++){
		NSLog(@"%@", [strings objectAtIndex:i]);
	}
	
    [pool drain];
    return 0;
}
When all is said and done NSMutableArray *strings will contain strings with the components like <.../> Of course this is highly dependent on the incoming data. However, using rangeOfString is as well as you will ignore data after the first occurrence of the string.
 
Thanks to all who replied. jared_kipe, the problem with your solution is there are several lines of text that precedes the appIDs.

I ended up using all the suggestions, and stored the output into an array (componentsSeparatedByString: @"\n"), then just doing a rangeOfString on each line in the array and tested subRange.location != NSNotFound. Its a little crappy, but seems to work. If anyone has any suggestions on a more efficient method, please let me know!

Code:
NSRange subRange;
NSString *tmpID, *appID;
NSString *outputStr = @"2010-05-28 18:28:22.256 someapp[91359:903] owner is: someuser\n2010-05-28 18:28:22.300 someapp[91359:903] <appID01 B6977A4E-782D-48AA-8F7D-395388493F4E /> <appID02 2EF30775-0F9D-465F-B012-283C695CB668 />\n";

NSArray *outputArray = [outputStr componentsSeparatedByString:@"\n"];
	for (NSString *tmpStr in outputArray) {
		subRange = [tmpStr rangeOfString: @"<appID02"];
		if ( subRange.location != NSNotFound) {
			NSLog(@"Found line: \n%@", tmpStr);
			NSLog(@"Location: %lu", subRange.location);
			tmpID = [tmpStr substringFromIndex:subRange.location];
			NSLog(@"tmpID = %@", tmpID);
			appID = [[tmpID stringByReplacingOccurrencesOfString:@"<appID02" withString:@""] 
					   stringByReplacingOccurrencesOfString: @"/>" withString: @""];
			NSLog(@"appID = %@", appID);
		}
	}

And again, thanks to all who replied. I kinda knew what I needed to do, but was stuck looking for a way to get a range based on a beginning and ending string.
 
I guess I thought that the second line in that log was what we were trying to parse. You are only interested in ones that begin with <appID02.. and don't care about the one that begins <appID01... very odd.

Well this seems to make the same assumptions you did... I think.

Code:
	NSString *input = @"2010-05-28 18:28:22.256 someapp[91359:903] owner is: someuser\n2010-05-28 18:28:22.300 someapp[91359:903] <appID01 B6977A4E-782D-48AA-8F7D-395388493F4E /> <appID02 2EF30775-0F9D-465F-B012-283C695CB668 />\n";
	NSScanner *scanner = [NSScanner scannerWithString: input];
	NSString *appID = nil;

	[scanner scanUpToString:@"<appID02 " intoString: NULL];
	[scanner scanUpToString:@" />" intoString: &appID];
	appID = [appID substringFromIndex: [@"<appID02 " length]];
	NSLog(@"appID = %@", appID);

EDIT: I just looked at the scanner documentation because I figured there was a method LIKE -scanPastString: which there is not Simplifies to
Code:
	NSString *input = @"2010-05-28 18:28:22.256 someapp[91359:903] owner is: someuser\n2010-05-28 18:28:22.300 someapp[91359:903] <appID01 B6977A4E-782D-48AA-8F7D-395388493F4E /> <appID02 2EF30775-0F9D-465F-B012-283C695CB668 />\n";
	NSScanner *scanner = [NSScanner scannerWithString: input];
	NSString *appID = nil;

	[scanner scanUpToString:@"<appID02 " intoString: NULL];
	[scanner scanString:@"<appID02 " intoString: NULL];
	[scanner scanUpToString:@" />" intoString: &appID];
	NSLog(@"appID = %@", appID);
 
appID01 and appID02 are different ids...appID01 is a child of appID02. I probably should have used the original id names instead of rename them for the post. Anyways, I will use appID02 to get the status of the batch, which includes appID01. Its possible to have many occurrences of ids like appID01, but only one appID02. And they are displayed on the same line, (<appID03 blah /> <appID02 blah />) after other stuff specific to the child appID is displayed.

Thanks jared_kipe for the scanner intro. I'll use that, as I dont need to store into an array and enumerate it. I'll just add a simple check to make sure it found the string I want and not simply the original string:

Code:
	NSString *input = @"2010-05-28 18:28:22.256 someapp[91359:903] owner is: someuser\n2010-05-28 18:28:22.300 someapp[91359:903] <appID01 B6977A4E-782D-48AA-8F7D-395388493F4E /> <appID02 2EF30775-0F9D-465F-B012-283C695CB668 />\n";
	NSScanner *scanner = [NSScanner scannerWithString: input];
	NSString *appID = nil;
	
	[scanner scanUpToString:@"<appID02 " intoString: NULL];
	if ([scanner isAtEnd] == YES) {
		NSLog(@"appID not found, exiting!");
		return 1;
	} else {
		[scanner scanString:@"<appID02 " intoString: NULL];
		[scanner scanUpToString:@" />" intoString: &appID];
		NSLog(@"appID = %@", appID);
		
	}

Thanks again!
 
Right, I follow that. Originally I thought the problem was, given a line with any number of <appIDxx blah /> in a row, break them into the pieces so that you could go through them later or something. Hence the need for an array, the idea that there could be 0+ of these.

If you're literally just looking for a string, and then what comes after it, a NSScanner is BY FAR the most efficient way of doing it. The scanner just marches through the array (string) until it finds what you're looking for and internally keeps track of where it is.

Much much better than taking one string, making it into several strings and storing pointers to all of them in an array and then going through each mini-string sequentially looking for the keyString.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.