How to get range of string with a specific start str and end str

Discussion in 'Mac Programming' started by Childs, May 28, 2010.

  1. Childs macrumors member

    Joined:
    May 28, 2010
    #1
    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?
     
  2. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #2
    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.
     
  3. larkost macrumors 6502a

    Joined:
    Oct 13, 2007
    #3
    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.
     
  4. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #4
    If you don't mind external code then RegexKitLite would work very well.
     
  5. jared_kipe macrumors 68030

    jared_kipe

    Joined:
    Dec 8, 2003
    Location:
    Seattle
    #5
    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.
     
  6. Childs thread starter macrumors member

    Joined:
    May 28, 2010
    #6
    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.
     
  7. jared_kipe macrumors 68030

    jared_kipe

    Joined:
    Dec 8, 2003
    Location:
    Seattle
    #7
    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);
    
     
  8. Childs thread starter macrumors member

    Joined:
    May 28, 2010
    #8
    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!
     
  9. jared_kipe macrumors 68030

    jared_kipe

    Joined:
    Dec 8, 2003
    Location:
    Seattle
    #9
    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.
     

Share This Page