I have a search string, where people can use quotes to group phrases together, and mix this with individual keywords. For example, a string like this:
"Something amazing" rooster
I'd like to separate that into an NSArray, so that it would have Something amazing (without quotes) as one element, and rooster as the other.
Neither componentsSeparatedByString nor componentsSeparatedByCharactersInSet seem to fit the bill. Is there an easy way to do this, or should I just code it up myself?
You probably will have to code some of this up yourself, but the NSScanner should be a good basis on which to build. If you use the scanUpToCharactersInSet method to look for everything up to your next whitespace or quote character to can pick off words. Once you encounter a quite character, you could continue to scan using just the quote in the character set to end at, so that spaces within the quotes don't result in the end of a token.
I made a simple way to do this using NSScanner:
+ (NSArray *)arrayFromTagString:(NSString *)string {
NSScanner *scanner = [NSScanner scannerWithString:string];
NSString *substring;
NSMutableArray *array = [[NSMutableArray alloc] init];
while (scanner.scanLocation < string.length) {
// test if the first character is a quote
unichar character = [string characterAtIndex:scanner.scanLocation];
if (character == '"') {
// skip the first quote and scan everything up to the next quote into a substring
[scanner setScanLocation:(scanner.scanLocation + 1)];
[scanner scanUpToString:#"\"" intoString:&substring];
[scanner setScanLocation:(scanner.scanLocation + 1)]; // skip the second quote too
}
else {
// scan everything up to the next space into the substring
[scanner scanUpToString:#" " intoString:&substring];
}
// add the substring to the array
[array addObject:substring];
//if not at the end, skip the space character before continuing the loop
if (scanner.scanLocation < string.length) [scanner setScanLocation:(scanner.scanLocation + 1)];
}
return array.copy;
}
This method will convert the array back to a tag string, re-quoting the multi-word tags:
+ (NSString *)tagStringFromArray:(NSArray *)array {
NSMutableString *string = [[NSMutableString alloc] init];
NSRange range;
for (NSString *substring in array) {
if (string.length > 0) {
[string appendString:#" "];
}
range = [substring rangeOfString:#" "];
if (range.location != NSNotFound) {
[string appendFormat:#"\"%#\"", substring];
}
else [string appendString:substring];
}
return string.description;
}
I ended up going with a regular expression as I was already using RegexKitLite, and creating this NSString+SearchExtensions category.
.h:
// NSString+SearchExtensions.h
#import <Foundation/Foundation.h>
#interface NSString (SearchExtensions)
-(NSArray *)searchParts;
#end
.m:
// NSString+SearchExtensions.m
#import "NSString+SearchExtensions.h"
#import "RegexKitLite.h"
#implementation NSString (SearchExtensions)
-(NSArray *)searchParts {
__block NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:5];
[self enumerateStringsMatchedByRegex:#"\\w+|\"[\\w\\s]*\"" usingBlock: ^(NSInteger captureCount,
NSString * const capturedStrings[captureCount],
const NSRange capturedRanges[captureCount],
volatile BOOL * const stop) {
NSString *result = [capturedStrings[0] stringByReplacingOccurrencesOfRegex:#"\"" withString:#""];
NSLog(#"Match: '%#'", result);
[items addObject:result];
}];
return [items autorelease];
}
#end
This returns an NSArray of strings with the search strings, removing the double quotes that surround the phrases.
If you'll allow a slightly different approach, you could try Dave DeLong's CHCSVParser. It is intended to parse CSV strings, but if you set the space character as the delimiter, I am pretty sure you will get the intended behavior.
Alternatively, you can peek into the code and see how it handles quoted fields - it is published under the MIT license.
I would run -componentsSeparatedByString:#"\"" first, then create a BOOL isPartOfQuote, initialized to YES if the first character of the string was a ", but otherwise set to NO.
Then create a mutable array to return:
NSMutableArray* masterArray = [[NSMutableArray alloc] init];
Then, create a loop over the array returned from the separation:
for(NSString* substring in firstSplitArray) {
NSArray* secondSplit;
if (isPartOfQuote == NO) {
secondSplit = [substring componentsSeparatedByString:#" "];
}
else {
secondSplit = [NSArray arrayWithObject: substring];
}
[masterArray addObjectsFromArray: secondSplit];
isPartOfQuote = !isPartOfQuote;
}
Then return masterArray from the function.
Related
I have an NSString which is a mathematical expression. I have operators (+,-,*,/) and operands (digits from 0 to 9,integers,decimals etc). I want to convert this NSString into NSArray. For example if my NSString is "7.9999-1.234*-9.21". I want NSArray having elements 7.9999,-,1.234,*,-,9.21 in the same order. How can I accomplish this?
I have tried a code. It dosent work in all scenarios though. Here It is:
code:
NSString *str=#"7.9999-1.234*-9.21";
NSMutableArray *marray=[[NSMutableArray alloc] init];
for(i=0;i<6;i++)
{
[marray addObject:[NSNull null]];
}
NSMutableArray *operands=[[NSMutableArray alloc] initWithObjects:#"7.9999",#"1.234",#"9.21",nil];
NSMutableArray *operators=[[NSMutableArray alloc] initWithObjects:#"-",#"*",#"-",nil];
for(i=0,j=0,k=0,l=0;i<=([str length]-1),j<[operands count],k<[operators count],l<[marray count];i++)
{
NSString *element=[[NSString alloc] initWithFormat:#"%c",[str characterAtIndex:i]];
BOOL res=[element isEqualToString:#"+"]||[element isEqualToString:#"-"]||[element isEqualToString:#"*"]||[element isEqualToString:#"/"];
if(res==0)
{
[marray replaceObjectAtIndex:l withObject:[operands objectAtIndex:j]];
}
else
{
l++;
[marray replaceObjectAtIndex:l withObject:[operators objectAtIndex:k]];
k++,l++,j++;
}
}
for(i=0;i<6;i++)
{
NSLog(#"%#",[marray objectAtIndex:i]);
}
Here str is the string to be converted. My array is the array obtained by converting the string str. When I execute this code I get the following on console:
7.9999
-
1.234
*
<null>
-
You should use NSScanner, scanning up to your operator characters, then when you find one, save the scanned string and then save the operator into the array and skip the operator (setScanLocation:). Continue doing this till you get to the end of the string (in a loop, one iteration for each operator).
NSArray * marray = [str componentsSeparatedByCharactersInSet:
[NSCharacterSet characterSetWithCharactersInString:#"+-*/"]
];
ThankYou #Wain and #Hinata Hyuga.I figured out a code that would work to convert any string to array with the help of your suggestions.
Here is the code
NSMutableArray *convArray=[[NSMutableArray alloc] init];
NSScanner *scanner = [NSScanner scannerWithString:inputString];
NSCharacterSet *opSet=[NSCharacterSet characterSetWithCharactersInString:#"+-/*"];
[scanner setCharactersToBeSkipped:opSet];
int i;
for(i=0;i<[inputString length];)
{
if([inputString characterAtIndex:i]=='+'||[inputString characterAtIndex:i]=='-'||[inputString characterAtIndex:i]=='*'||[inputString characterAtIndex:i]=='/')
{
[convArray addObject:[[NSString alloc] initWithFormat:#"%c",[inputString characterAtIndex:i]]];
i++;
}
else
{
NSString *oprnd;
[scanner scanUpToCharactersFromSet:opSet intoString:&oprnd];
[convArray addObject:oprnd];
i=i+[inputString rangeOfString:oprnd].length;
}
}
return convArray;
I am trying to parse a large string in order to isolate words and all punctuation. Java has the following constructor for its StringTokenizer class.
public StringTokenizer(String str, String delim, boolean returnDelims)
Notice the last parameter. If that is true, each delimiter is also returned as a token.
Is there a class in Obj-C that mimics this Java functionality? I have been able to parse the string, but I lose my delimiters in the process and those delimiters determine what I do next.
According to the CFStringTokenizer reference, it tokenizes into "words, sentences, and paragraphs". I need more granularity than that.
Appreciate the help.
You can just use the componentsSeparatedByString: method of NSString and then NSMutableArray to insert the delimiters between the substrings:
NSString *s = #"abc,def,ghi,jkl";
NSString *delim = #",";
NSArray *arr = [s componentsSeparatedByString:delim];
NSMutableArray *res = [NSMutableArray array];
[res addObject:arr[0]];
for (NSInteger i = 1; i < arr.count; i++) {
[res addObject:delim];
[res addObject:arr[i]];
}
NSLog(#"%#", res);
Here is a sample category on NSScanner that may get you started:
#implementation NSScanner (Tokenizer)
+ (NSArray *)tokenize(NSString *str,NSString *delim,BOOL returnDelims)
{
NSScanner *scanner=[NSScanner scannerWithString:str];
NSString *delimiters=[NSCharacterSet characterSetWithCharactersInString:#",.!;"];
NSMutableArray *ma=[NSMutableArray array];
NSString *s;
while(![scanner isAtEnd])
{
if([scanner scanUpToCharactersFromSet:delim intoString:&s])
{
[ma addObject:s];
}
if([scanner scanCharactersFromSet:delim intoString:&s])
{
if(returnDelims) [ma addObject:s];
}
}
return ma;
}
#end
This isn't a complete implementation, it doesn't deal with whitespace or enforcing a specific order in the array. But it should give you an idea.
I'm trying to cut the length of an NSString without splitting the last word with this method:
// cut a string by words
- (NSString* )stringCutByWords:(NSString *)string toLength:(int)length;
{
// search backwards in the string for the beginning of the last word
while ([string characterAtIndex:length] != ' ' && length > 0) {
length--;
}
// if the last word was the first word of the string search for the end of the word
if (length <= 0){
while ([string characterAtIndex:length] != ' ' && length > string.length-1) {
length++;
}
}
// define the range you're interested in
NSRange stringRange = {0, length};
// adjust the range to include dependent chars
stringRange = [string rangeOfComposedCharacterSequencesForRange:stringRange];
// Now you can create the short string
string = [string substringWithRange:stringRange];
return [NSString stringWithFormat:#"%#...",string];
}
now my question is:
Is there a build-in way in objective-c or cocoa-touch which i did not see or else is there a "nicer" way to do this because iam not very happy with this solution.
greetings and thanks for help
C4rmel
My proposal for a Category method
#interface NSString (Cut)
-(NSString *)stringByCuttingExceptLastWordWithLength:(NSUInteger)length;
#end
#implementation NSString (Cut)
-(NSString *)stringByCuttingExceptLastWordWithLength:(NSUInteger)length
{
__block NSMutableString *newString = [NSMutableString string];
NSArray *components = [self componentsSeparatedByString:#" "];
if ([components count] > 0) {
NSString *lastWord = [components objectAtIndex:[components count]-1];
[components enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) {
if (([obj length]+[newString length] + [lastWord length] + 2) < length) {
[newString appendFormat:#" %#", obj];
} else {
[newString appendString:#"…"];
[newString appendFormat:#" %#", lastWord];
*stop = YES;
}
}];
}
return newString;
}
Usage:
NSString *string = #"Hello World! I am standing over here! Can you see me?";
NSLog(#"%#", [string stringByCuttingExceptLastWordWithLength:25]);
Suggestions:
make it a category method;
use NSCharacterSet and the built-in search methods rather than rolling your own.
So:
/* somewhere public */
#interface NSString (CutByWords)
- (NSString *)stringCutByWordsToMaxLength:(int)length
#end
/* in an implementation file, somewhere */
#implementation NSString (CutByWords)
// cut a string by words
- (NSString *)stringCutByWordsToMaxLength:(int)length
{
NSCharacterSet *whitespaceCharacterSet =
[NSCharacterSet whitespaceCharacterSet];
// to consider: a range check on length here?
NSRange relevantRange = NSMakeRange(0, length);
// find beginning of last word
NSRange lastWordRange =
[self rangeOfCharacterFromSet:whitespaceCharacterSet
options:NSBackwardsSearch
range:relevantRange];
// if the last word was the first word of the string,
// consume the whole string; this looks to be the same
// effect as the original scan forward given that the
// assumption is already made in the scan backwards that
// the string doesn't end on a whitespace; if I'm wrong
// then get [whitespaceCharacterSet invertedSet] and do
// a search forwards
if(lastWordRange.location == NSNotFound)
{
lastWordRange = relevantRange;
}
// adjust the range to include dependent chars
stringRange = [self rangeOfComposedCharacterSequencesForRange:stringRange];
// Now you can create the short string
NSString *string = [self substringWithRange:stringRange];
return [NSString stringWithFormat:#"%#...",string];
}
#end
/* subsequently */
NSString *string = ...whatever...;
NSString *cutString = [string stringCutByWordsToMaxLength:100];
I have a string as shown below,
NSString * aString = #"This is the #substring1 and #subString2 I want";
How can I select only the text starting with '#' (and ends with a space), in this case 'subString1' and 'subString2'?
Note: Question was edited for clarity
You can do this using an NSScanner to split the string up. This code will loop through a string and fill an array with substrings.
NSString * aString = #"This is the #substring1 and #subString2 I want";
NSMutableArray *substrings = [NSMutableArray new];
NSScanner *scanner = [NSScanner scannerWithString:aString];
[scanner scanUpToString:#"#" intoString:nil]; // Scan all characters before #
while(![scanner isAtEnd]) {
NSString *substring = nil;
[scanner scanString:#"#" intoString:nil]; // Scan the # character
if([scanner scanUpToString:#" " intoString:&substring]) {
// If the space immediately followed the #, this will be skipped
[substrings addObject:substring];
}
[scanner scanUpToString:#"#" intoString:nil]; // Scan all characters before next #
}
// do something with substrings
[substrings release];
Here is how the code works:
Scan up to a #. If it isn't found, the scanner will be at the end of the string.
If the scanner is at the end of the string, we are done.
Scan the # character so that it isn't in the output.
Scan up to a space, with the characters that are scanned stored in substring. If either the # was the last character, or was immediately followed by a space, the method will return NO. Otherwise it will return YES.
If characters were scanned (the method returned YES), add substring to the substrings array.
GOTO 1
[aString substringWithRange:NSMakeRange(13, 10)]
would give you substring1
You can calculate the range using:
NSRange startRange = [aString rangeOfString:#"#"];
NSRange endRange = [original rangeOfString:#"1"];
NSRange searchRange = NSMakeRange(startRange.location , endRange.location);
[aString substringWithRange:searchRange]
would give you substring1
Read more:
Position of a character in a NSString or NSMutableString
and
http://iosdevelopertips.com/cocoa/nsrange-and-nsstring-objects.html
Pretty simple, easy to understand version avoiding NSRange stuff:
NSArray * words = [string componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSMutableArray * mutableWords = [NSMutableArray new];
for (NSString * word in words){
if ([word length] > 1 && [word characterAtIndex:0] == '#'){
NSString * editedWord = [word substringFromIndex:1];
[mutableWords addObject:editedWord];
}
}
Assuming that you are looking to find the first string that starts with a pound, and ends with a space, this might work. I don't have XCode in front of me, so forgive me if there's a syntax error or length off by 1 somewhere:
-(NSString *)StartsWithPound:(NSString *)str {
NSRange range = [str rangeOfString:#"#"];
if(range.length) {
NSRange rangeend = [str rangeOfString:#" " options:NSLiteralSearch range:NSMakeRange(range.location,[str length] - range.location - 1)];
if(rangeend.length) {
return [str substringWithRange:NSMakeRange(range.location,rangeend.location - range.location)];
}
else
{
return [str substringFromIndex:range.location];
}
}
else {
return #"";
}
}
Another simple solution:
NSRange hashtag = [aString rangeOfString:#"#"];
NSRange word = [[aString substringFromIndex:hashtag.location] rangeOfString:#" "];
NSString *hashtagWord = [aString substringWithRange:NSMakeRange(hashtag.location, word.location)];
This is what I'd do:
NSString *givenStringWithWhatYouNeed = #"What you want to look through";
NSArray *listOfWords = [givenStringWithWhatYouNeed componentsSeparatedByString:#" "];
for (NSString *word in listOfWords) {
if ([[word substringWithRange:NSMakeRange(0, 1)]isEqualToString:#"#"]) {
NSString *whatYouWant = [[word componentsSeparatedByString:#"#"]lastObject];
}
}
Then you can do what you need with the whatYouWant instances. If you want to know which string it is (if it's the substring 1 or 2), check the index of of word string in the listOfWords array.
I hope this helps.
A general and simple code to select all the words starting with "#" in a NSString is:
NSString * aString = #"This is the #substring1 and #subString2 ...";
NSMutableArray *selection=#[].mutableCopy;
while ([aString rangeOfString:#"#"].location != NSNotFound)
{
aString = [aString substringFromIndex:[aString rangeOfString:#"#"].location +1];
NSString *item=([aString rangeOfString:#" "].location != NSNotFound)?[aString substringToIndex:[aString rangeOfString:#" "].location]:aString;
[selection addObject:item];
}
if you still need the original string you can do a copy.
The inline conditional is used in case your selected item is the last word
I was wondering how to capitalize a string found in an object in an NSMutableArray.
An NSArray contains the string 'April' at index 2.
I want this to be changed to 'APRIL'.
Is there something simple like this?
viewNoteDateMonth.text = [[displayDate objectAtIndex:2] capitalized];
Here ya go:
viewNoteDateMonth.text = [[displayDate objectAtIndex:2] uppercaseString];
Btw:
"april" is lowercase ➔ [NSString lowercaseString]
"APRIL" is UPPERCASE ➔ [NSString uppercaseString]
"April May" is Capitalized/Word Caps ➔ [NSString capitalizedString]
"April may" is Sentence caps ➔ (method missing; see workaround below)
Hence what you want is called "uppercase", not "capitalized". ;)
As for "Sentence Caps" one has to keep in mind that usually "Sentence" means "entire string". If you wish for real sentences use the second method, below, otherwise the first:
#interface NSString ()
- (NSString *)sentenceCapitalizedString; // sentence == entire string
- (NSString *)realSentenceCapitalizedString; // sentence == real sentences
#end
#implementation NSString
- (NSString *)sentenceCapitalizedString {
if (![self length]) {
return [NSString string];
}
NSString *uppercase = [[self substringToIndex:1] uppercaseString];
NSString *lowercase = [[self substringFromIndex:1] lowercaseString];
return [uppercase stringByAppendingString:lowercase];
}
- (NSString *)realSentenceCapitalizedString {
__block NSMutableString *mutableSelf = [NSMutableString stringWithString:self];
[self enumerateSubstringsInRange:NSMakeRange(0, [self length])
options:NSStringEnumerationBySentences
usingBlock:^(NSString *sentence, NSRange sentenceRange, NSRange enclosingRange, BOOL *stop) {
[mutableSelf replaceCharactersInRange:sentenceRange withString:[sentence sentenceCapitalizedString]];
}];
return [NSString stringWithString:mutableSelf]; // or just return mutableSelf.
}
#end
viewNoteDateMonth.text = [[displayDate objectAtIndex:2] uppercaseString];
Documentation: http://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/Reference/NSString.html#//apple_ref/occ/instm/NSString/uppercaseString
You can also use lowercaseString and capitalizedString
In case anyone needed the above in swift :
SWIFT 3.0 and above :
this will capitalize your string, make the first letter capital :
viewNoteDateMonth.text = yourString.capitalized
this will uppercase your string, make all the string upper case :
viewNoteDateMonth.text = yourString.uppercased()