Objective c Split String Issue - ios

I'm trying to split a string into an NSArray, however I'm having an issue ignoring strings that have speech marks in.
For example
NSString *example = #"100 200 300 \"TEST ITEM\" 500";
NSArray *array = [example componentsSeparatedByString:#" "];
However the array obviously gets created as 100, 100, 300, "Test, ITEM", 500. Is it possible to treat anything with " " as one?
Thanks

You could use a well known CSV Parser which can be configured to use spaces as separators, and will treat double-quotes correctly. That assumes you want usual CSV-handling like escaping of double-quotes inside double-quoted strings, etc.
Otherwise, you'll probably need to write the parsing logic yourself; take a look at NSScanner, which would let you read up to a space or double-quote, look at what you get back, and then read to the next space/double-quote, etc.
Example:
#interface ItemParser : NSObject<CHCSVParserDelegate>
- (NSArray *)itemsFromString:(NSString *)input;
#end
#implementation ItemParser {
NSMutableArray *_results;
}
- (NSArray *)itemsFromString:(NSString *)input {
_results = [NSMutableArray array];
NSStringEncoding encoding = [input fastestEncoding];
NSInputStream *stream = [NSInputStream inputStreamWithData:[input dataUsingEncoding:encoding]];
CHCSVParser *parser = [[CHCSVParser alloc] initWithInputStream:stream usedEncoding:&encoding delimiter:' '];
parser.delegate = self;
[parser parse];
return _results;
}
- (void)parser:(CHCSVParser *)parser didReadField:(NSString *)field atIndex:(NSInteger)fieldIndex {
[_results addObject:field];
}
#end
You'd use it like this:
NSString *example = #"100 200 300 \"TEST ITEM\" 500";
ItemParser *parser = [[ItemParser alloc] init];
NSLog(#"%#", [parser itemsFromString:example]);

Here's an NSString category that uses an NSScanner to split a string into search terms, respecting various types of quote pairs: "" '' ‘’ “”
Usage:
NSArray *terms = [#"This is my \"search phrase\" I want to split" searchTerms];
// results in: ["This", "is", "my", "search phrase", "I", "want", "to", "split"]
Code:
#interface NSString (Search)
- (NSArray *)searchTerms;
#end
#implementation NSString (Search)
- (NSArray *)searchTerms {
// Strip whitespace and setup scanner
NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSString *searchString = [self stringByTrimmingCharactersInSet:whitespace];
NSScanner *scanner = [NSScanner scannerWithString:searchString];
[scanner setCharactersToBeSkipped:nil]; // we'll handle whitespace ourselves
// A few types of quote pairs to check
NSDictionary *quotePairs = #{#"\"": #"\"",
#"'": #"'",
#"\u2018": #"\u2019",
#"\u201C": #"\u201D"};
// Scan
NSMutableArray *results = [[NSMutableArray alloc] init];
NSString *substring = nil;
while (scanner.scanLocation < searchString.length) {
// Check for quote at beginning of string
unichar unicharacter = [self characterAtIndex:scanner.scanLocation];
NSString *startQuote = [NSString stringWithFormat:#"%C", unicharacter];
NSString *endQuote = [quotePairs objectForKey:startQuote];
if (endQuote != nil) { // if it's a valid start quote we'll have an end quote
// Scan quoted phrase into substring (skipping start & end quotes)
[scanner scanString:startQuote intoString:nil];
[scanner scanUpToString:endQuote intoString:&substring];
[scanner scanString:endQuote intoString:nil];
} else {
// Single word that is non-quoted
[scanner scanUpToCharactersFromSet:whitespace intoString:&substring];
}
// Process and add the substring to results
if (substring) {
substring = [substring stringByTrimmingCharactersInSet:whitespace];
if (substring.length) [results addObject:substring];
}
// Skip to next word
[scanner scanCharactersFromSet:whitespace intoString:nil];
}
// Return non-mutable array
return results.copy;
}
#end

Related

Objective C - How to compare string from the console

My code always returns "fail" for the following string comparison using isEqualToString. What is the correct way to compare strings coming from the console?
char buf[MAX_LENGTH];
fgets(buf, MAX_LENGTH, stdin);
NSString *s = [[NSString alloc] initWithUTF8String:buf];
NSLog(#"You typed %#", s);
NSString *n = #"exit";
if ([n isEqualToString:s]) {
NSLog(#"success!");
} else {
NSLog(#"fail");
}
The result of fgets contains "\n", So you need define your "n" as this:
NSString *n = #"exit\n";
Or remove the "\n" from "s":
NSString *s = [[NSString alloc] initWithUTF8String:buf];
s = [s stringByReplacingOccurrencesOfString:#"\n" withString:#""];

Extract one word from a two word string

I have a two word string in another view controller containing a user defined first and last name
NSString *userName = ([self hasAttributeWithName:kContractorName] ? [self attributeWithName:kContractorName].value : [self.certificate.contractor.name uppercaseString]);
when retrieving this string in another view controller I want to extract only the first name.
I researched SO on using scanner and found a very helpful answer here: Objective C: How to extract part of a String (e.g. start with '#'), and im almost there.
The problem is I can only seem extract the second name with my variation on the origial code. Im scanning my string up to the space between first and second name, this returns the second name fine. Just need a nudge now on how to set this to extract the first name instead of the second
NSMutableArray *substrings = [NSMutableArray new];
NSScanner *scanner = [NSScanner scannerWithString:userName];
[scanner scanUpToString:#" " intoString:nil]; // Scan all characters before
while(![scanner isAtEnd]) {
NSString *name = nil;
[scanner scanString:#" " intoString:nil]; // Scan the character
if([scanner scanUpToString:#" " intoString:&name]) {
// If the space immediately followed the , this will be skipped
[substrings addObject:name];
}
[scanner scanUpToString:#" " intoString:nil]; // Scan all characters before next
}
Better use NSString's componentsSeparatedByString method:
NSString* firstName = [userName componentsSeparatedByString:#" "][0];
If first and last name are separated with a space you can use:
NSArray *terms = [userName componentsSeparatedByString:#" "];
NSString *firstName = [terms objectAtIndex:0];
You could just split the string into first and last names using componentsSeparatedByString.
NSArray *subStrings = [userName componentsSeparatedByString:#" "];
NSString *firstName = [subStrings objectAtIndex:0];
Sure, you can just split the string by spaces and take the first element, but where’s the fun in that? Try NSLinguisticTagger to actually split this using a Cocoa API:
__block NSString *firstWord = nil;
NSString *question = #"What is the weather in San Francisco?";
NSLinguisticTaggerOptions options = NSLinguisticTaggerOmitWhitespace | NSLinguisticTaggerOmitPunctuation | NSLinguisticTaggerJoinNames;
NSLinguisticTagger *tagger = [[NSLinguisticTagger alloc] initWithTagSchemes: [NSLinguisticTagger availableTagSchemesForLanguage:#"en"] options:options];
tagger.string = question;
[tagger enumerateTagsInRange:NSMakeRange(0, [question length]) scheme:NSLinguisticTagSchemeNameTypeOrLexicalClass options:options usingBlock:^(NSString *tag, NSRange tokenRange, NSRange sentenceRange, BOOL *stop) {
firstWord = [question substringWithRange:tokenRange];
*stop = YES;
}];

Split NSString and Limit the response

I have a string Hello-World-Test, I want to split this string by the first dash only.
String 1:Hello
String 2:World-Test
What is the best way to do this? What I am doing right now is use componentsSeparatedByString, get the first object in the array and set it as String 1 then perform substring using the length of String 1 as the start index.
Thanks!
I added a category on NSString to split on the first occurrence of a given string. It may not be ideal to return the results in an array, but otherwise it seems fine. It just uses the NSString method rangeOfString:, which takes an NSString(B) and returns an NSRange showing where that string(B) is located.
#interface NSString (Split)
- (NSArray *)stringsBySplittingOnString:(NSString *)splitString;
#end
#implementation NSString (Split)
- (NSArray *)stringsBySplittingOnString:(NSString *)splitString
{
NSRange range = [self rangeOfString:splitString];
if (range.location == NSNotFound) {
return nil;
} else {
NSLog(#"%li",range.location);
NSLog(#"%li",range.length);
NSString *string1 = [self substringToIndex:range.location];
NSString *string2 = [self substringFromIndex:range.location+range.length];
NSLog(#"String1 = %#",string1);
NSLog(#"String2 = %#",string2);
return #[string1, string2];
}
}
#end
Use rangeOfString to find if split string exits and then use substringWithRange to create new string on bases of NSRange.
For Example :
NSString *strMain = #"Hello-World-Test";
NSRange match = [strMain rangeOfString:#"-"];
if(match.location != NSNotFound)
{
NSString *str1 = [strMain substringWithRange: NSMakeRange (0, match.location)];
NSLog(#"%#",str1);
NSString *str2 = [strMain substringWithRange: NSMakeRange (match.location+match.length,(strMain.length-match.location)-match.length)];
NSLog(#"%#",str2);
}

How to import an excel file that contains line breaks with-in the cell text

I have an excel file with some data (with multiple columns and multiple rows). I want to import this data into my core-data based database. I'm struggling to import the data, because the text in the cells contain line breaks.
I've tried the following:
1.) Export excel to Tab-delimited text file
2.) Write an import route in iOS that reads the tab-delimited text file using the following code:
NSCharacterSet *tabCharacterSet = [NSCharacterSet characterSetWithCharactersInString:#"\t"];
NSArray *rows = [dataString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSArray *columns = [row componentsSeparatedByCharactersInSet:tabCharacterSet];
Problem: I have 1000 rows with 15 columns each. The parsing routine returns more than 1000 rows and less than 15 columns. The line breaks with-in the cell are not being handled properly by the parsing routine.
I get the same results if I use
[NSCharacterSet characterSetWithCharactersInString:#"\r\n"];
or
[NSCharacterSet characterSetWithCharactersInString:#"\r"];
instead of
[NSCharacterSet newlineCharacterSet]
but it completely fails if I use
[NSCharacterSet characterSetWithCharactersInString:#"\n"];
How can I parse the excel-data properly?
Perhaps I can use Regex expressions to get line components? Any idea/pointers?
UPDATE (Sample XLSX and Export File):
The solution that worked for me involves using NSScanner class.
- (NSArray *)parseCSVFileString {
NSMutableArray *rows = [NSMutableArray array];
// Get newline character set
NSMutableCharacterSet *newlineCharacterSet = (id)[NSMutableCharacterSet whitespaceAndNewlineCharacterSet];
[newlineCharacterSet formIntersectionWithCharacterSet:[[NSCharacterSet whitespaceCharacterSet] invertedSet]];
// Characters that are important to the parser
NSMutableCharacterSet *importantCharactersSet = (id)[NSMutableCharacterSet characterSetWithCharactersInString:#",\""];
[importantCharactersSet formUnionWithCharacterSet:newlineCharacterSet];
// Create scanner, and scan string
NSScanner *scanner = [NSScanner scannerWithString:self];
[scanner setCharactersToBeSkipped:nil];
while (![scanner isAtEnd]) {
#autoreleasepool {
BOOL insideQuotes = NO;
BOOL finishedRow = NO;
NSMutableArray *columns = [NSMutableArray arrayWithCapacity:10];
NSMutableString *currentColumn = [NSMutableString string];
while ( !finishedRow ) {
NSString *tempString;
if ([scanner scanUpToCharactersFromSet:importantCharactersSet intoString:&tempString]) {
[currentColumn appendString:tempString];
}
if ([scanner isAtEnd]) {
if (![currentColumn isEqualToString:#""]) [columns addObject:currentColumn];
finishedRow = YES;
} else if ([scanner scanCharactersFromSet:newlineCharacterSet intoString:&tempString]) {
if (insideQuotes) {
// Add line break to column text
[currentColumn appendString:tempString];
} else {
// End of row
if (![currentColumn isEqualToString:#""]) [columns addObject:currentColumn];
finishedRow = YES;
}
} else if ([scanner scanString:#"\"" intoString:NULL]) {
if (insideQuotes && [scanner scanString:#"\"" intoString:NULL]) {
// Replace double quotes with a single quote in the column string.
[currentColumn appendString:#"\""];
} else {
// Start or end of a quoted string.
insideQuotes = !insideQuotes;
}
} else if ([scanner scanString:#"," intoString:NULL]) {
if (insideQuotes) {
[currentColumn appendString:#","];
} else {
// This is a column separating comma
[columns addObject:currentColumn];
currentColumn = [NSMutableString string];
[scanner scanCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:NULL];
}
}
}
if ( [columns count] > 0 ) [rows addObject:columns];
}
}
return rows;
}
Reference: http://www.macresearch.org/cocoa-scientists-part-xxvi-parsing-csv-data

Return number from NSString

I have NSString like this: #"text 932".
How I can return number from this string. Number is always at the end of string, but i can' t use stringWithRange, because number don' t have constant length. So I'm seeking for better method.
I aslo want' know how to return number from string like this #"text 3232 text". I aslo don' t know position of number.
There is any function that find number in string ?
Here is a solution that will work for both strings
NSString *myString = #"text 3232 text";
//Create a scanner with the string
NSScanner *scanner = [NSScanner scannerWithString:myString];
//Create a character set that includes all letters, whitespaces, and newlines
//These will be used as skip tokens
NSMutableCharacterSet *charactersToBeSkipped = [[NSMutableCharacterSet alloc]init];
[charactersToBeSkipped formUnionWithCharacterSet:[NSCharacterSet letterCharacterSet]];
[charactersToBeSkipped formUnionWithCharacterSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[scanner setCharactersToBeSkipped:charactersToBeSkipped];
[charactersToBeSkipped release];
//Create an int to hold the number
int i;
//Do the work
if ([scanner scanInt:&i]) {
NSLog(#"i = %d", i);
}
The output of the NSLog is
i = 3232
EDIT
To handle decimals:
float f;
if ([scanner scanFloat:&f]) {
NSLog(#"f = %f", f);
}
Update:
Updated to test whether there is a match or not, and also to handle negative/decimal numbers
NSString *inputString=#"text text -9876.234 text";
NSString *regExprString=#"-{0,1}\\d*\\.{0,1}\\d+";
NSRegularExpression * regex = [NSRegularExpression regularExpressionWithPattern:regExprString options:NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators error:nil];
NSRange rangeOfFirstMatch=[regex firstMatchInString:inputString options:0 range:NSMakeRange(0, inputString.length)].range;
if(rangeOfFirstMatch.length>0){
NSString *firstMatch=[inputString substringWithRange:rangeOfFirstMatch];
NSLog(#"firstmatch:%#",firstMatch);
}
else{
NSLog(#"No Match");
}
Original:
Here is a solution that uses regular expressions:
NSString *inputString=#"text text 0123456 text";
NSString *regExprString=#"[0-9]+";
NSRegularExpression * regex = [NSRegularExpression regularExpressionWithPattern:regExprString options:NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators error:nil];
NSString *firstMatch=[inputString substringWithRange:[regex firstMatchInString:inputString options:0 range:NSMakeRange(0, inputString.length)].range];
NSLog(#"%#",firstMatch);
output is:
0123456
If you want an actual integer from that, you can add:
NSInteger i=[firstMatch integerValue];
NSLog(#"%d",i);
Output is then: 123456

Resources