NSScanner scanUpToCharactersFromSet gives BAD ACCESS - iphone

NSString*test=[NSString stringWithString:inputString];
NSScanner*scan;
BOOL pass= [scan scanUpToCharactersFromSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet] intoString:&test];
The last line crashes the app with bad access. Does it have something to do with the address symbol, &? I'm not clear on why it needs this type of syntax anyway.
I am trying to simply check with my BOOL if the inputString contains any non-alphanumeric characters. If it does, pass becomes YES.
UPDATE: I see I do not understand scanner entirely. I understand this output:
NSString*test=#"Hello"; // this should cause my BOOL to be NO since no characters are scanned
NSScanner*scanix=[NSScanner scannerWithString:test];
BOOL pass= [scanix scanUpToCharactersFromSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet] intoString:nil];
NSLog(#"pass is %i for %#",pass, test);
Result in log: pass is 1 for Hello
What I want is to know if in fact the test string contains non-alphanumeric characters. How would I factor such a test into this?

You are supposed to initialise your scanner with the string to parse,
NSScanner *scan =[NSScanner scannerWithString:mystring];

What I want is to know if in fact the test string contains
non-alphanumeric characters. How would I factor such a test into this?
You don't need to use NSScanner if this is your only goal. You can simply use NSString's -rangeOfCharacterFromSet: method:
NSCharacterSet *nonAlphanumeric = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
pass = ([test rangeOfCharacterFromSet:nonAlphanumeric].location != NSNotFound);

You are mixing up the destination string and the source string. Your source is test. Also, your scanner is not initialized.
NSScanner *scan = [NSScanner scannerWithString:test];
BOOL didScanCharacters = [scan scanUpToCharactersFromSet:
[[NSCharacterSet alphanumericCharacterSet] invertedSet]
intoString:nil];
The EXC_BAD_ACCESS error occurs because you are sending a message to a nonexistent object.

Related

NSCharacter Set uses int's but i need unassigned short?

I am using MWFeedParser to add a feed into my app. Now the framework passes date's and I it has a few warnings mainly due to older type of code.
Now there are 4 warnings left which are all the same and technically I can fix them and remove them so that the warnings are gone, but then I get left with the app not working properly.
The code concerning is:
// Character sets
NSCharacterSet *stopCharacters = [NSCharacterSet characterSetWithCharactersInString:[NSString stringWithFormat:#"< \t\n\r%C%C%C%C", 0x0085, 0x000C, 0x2028, 0x2029]];
Now the bit that is the warning is:
\t\n\r%C%C%C%C", 0x0085, 0x000C, 0x2028, 0x2029]];
The warning is:
Format specifies type 'unsigned short' but the argument has type 'int'
So I changed into:
\t\n\r%i%i%i%i", 0x0085, 0x000C, 0x2028, 0x2029]];
which indeed removed the warnings and gave me perfect code:-) (no warnings or errors)
When I then ran the app it did not parse the date and it was not able to open the link.
I am not sure if this a is C thing, but right now it is definitely outside of my knowledge field. Is there anyone who can help me that can fix this problem, and still have it working in the app??
Thank you in advance:-)
EDIT
- (NSString *)stringByConvertingHTMLToPlainText {
// Pool
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Character sets
NSCharacterSet *stopCharacters = [NSCharacterSet characterSetWithCharactersInString:#"< \t\n\r\x0085\x000C\u2028\u2029"];
NSCharacterSet *newLineAndWhitespaceCharacters = [NSCharacterSet characterSetWithCharactersInString:#"< \t\n\r\205\014\u2028\u2029"];
NSCharacterSet *tagNameCharacters = [NSCharacterSet characterSetWithCharactersInString:#"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"];
// Scan and find all tags
NSMutableString *result = [[NSMutableString alloc] initWithCapacity:self.length];
NSScanner *scanner = [[NSScanner alloc] initWithString:self];
[scanner setCharactersToBeSkipped:nil];
[scanner setCaseSensitive:YES];
NSString *str = nil, *tagName = nil;
BOOL dontReplaceTagWithSpace = NO;
do {
// Scan up to the start of a tag or whitespace
if ([scanner scanUpToCharactersFromSet:stopCharacters intoString:&str]) {
[result appendString:str];
str = nil; // reset
}
// Check if we've stopped at a tag/comment or whitespace
if ([scanner scanString:#"<" intoString:NULL]) {
// Stopped at a comment or tag
if ([scanner scanString:#"!--" intoString:NULL]) {
// Comment
[scanner scanUpToString:#"-->" intoString:NULL];
[scanner scanString:#"-->" intoString:NULL];
} else {
// Tag - remove and replace with space unless it's
// a closing inline tag then dont replace with a space
if ([scanner scanString:#"/" intoString:NULL]) {
// Closing tag - replace with space unless it's inline
tagName = nil; dontReplaceTagWithSpace = NO;
if ([scanner scanCharactersFromSet:tagNameCharacters intoString:&tagName]) {
tagName = [tagName lowercaseString];
dontReplaceTagWithSpace = ([tagName isEqualToString:#"a"] ||
[tagName isEqualToString:#"b"] ||
[tagName isEqualToString:#"i"] ||
[tagName isEqualToString:#"q"] ||
[tagName isEqualToString:#"span"] ||
[tagName isEqualToString:#"em"] ||
[tagName isEqualToString:#"strong"] ||
[tagName isEqualToString:#"cite"] ||
[tagName isEqualToString:#"abbr"] ||
[tagName isEqualToString:#"acronym"] ||
[tagName isEqualToString:#"label"]);
}
// Replace tag with string unless it was an inline
if (!dontReplaceTagWithSpace && result.length > 0 && ![scanner isAtEnd]) [result appendString:#" "];
}
// Scan past tag
[scanner scanUpToString:#">" intoString:NULL];
[scanner scanString:#">" intoString:NULL];
}
} else {
// Stopped at whitespace - replace all whitespace and newlines with a space
if ([scanner scanCharactersFromSet:newLineAndWhitespaceCharacters intoString:NULL]) {
if (result.length > 0 && ![scanner isAtEnd]) [result appendString:#" "]; // Dont append space to beginning or end of result
}
}
} while (![scanner isAtEnd]);
// Cleanup
[scanner release];
// Decode HTML entities and return
NSString *retString = [[result stringByDecodingHTMLEntities] retain];
[result release];
// Drain
[pool drain];
// Return
return [retString autorelease];
}
This is a total mess
The reason this is a total mess is because you are running into a compiler bug and an arbitrary limitation in the C spec.
Scroll to the bottom for the fix.
Compiler warning
Format specifies type 'unsigned short' but the argument has type 'int'
My conclusion is that this is a compiler bug in Clang. It is definitely safe to ignore this warning, because (unsigned short) arguments are always promoted to (int) before they are passed to vararg functions anyway. This is all stuff that is in the C standard (and it applies to Objective C, too).
printf("%hd", 1); // Clang generates warning. GCC does not.
// Clang is wrong, GCC is right.
printf("%hd", 1 << 16); // Clang generates warning. GCC does not.
// Clang is right, GCC is wrong.
The problem here is that neither compiler looks deep enough.
Remember, it is actually impossible to pass a short to printf(), because it must get promoted to int. GCC never gives a warning for constants, Clang ignores the fact that you are passing a constant and always gives a warning because the type is wrong. Both options are wrong.
I suspect nobody has noticed because -- why would you be passing a constant expression to printf() anyway?
In the short term, you can use the following hack:
#pragma GCC diagnostic ignored "-Wformat"
Universal character names
You can use \uXXXX notation. Except you can't, because the compiler won't let you use U+0085 this way. Why? See § 6.4.3 of C99:
A universal character name shall not specify a character whose short identifier is less than 00A0 other than 0024 ($), 0040 (#), or 0060 (‘), nor one in the range D800 through DFFF inclusive.
This rules out \u0085.
There is a proposal to fix this part of the spec.
The fix
You really want a constant string, don't you? Use this:
[NSCharacterSet characterSetWithCharactersInString:
#"\t\n\r\xc2\x85\x0c\u2028\u2029"]
This relies on the fact that the source encoding is UTF-8. Don't worry, that's not going to change any time soon.
The \xc2\x85 in the string is the UTF-8 encoding of U+0085. The appearance of 85 in both is a coincidence.
The problem is that 0x0085, etc are literal ints. So they don't match the %C format specifier, which expects a unichar, which is an unsigned short.
There's no direct way to specify a literal short in C and I'm not aware of any Objective-C extension. But you can use a brute-force approach:
NSCharacterSet *stopCharacters =
[NSCharacterSet characterSetWithCharactersInString:
[NSString stringWithFormat:#"< \t\n\r%C%C%C%C",
(unichar)0x0085, (unichar)0x000C,
(unichar)0x2028, (unichar)0x2029]];
You don't need stringWithFormat, you can embed unicode chars directly into a string using the \u escape. For example \u0085.

regular expression to match " but not \"

How can I construct a regular expression which matches an literal " but only if it is not preceded by the escape slash namely \
I have a NSMutableString str which prints the following on NSLog. The String is received from a server online.
"Hi, check out \"this book \". Its cool"
I want to change it such that it prints the following on NSLog.
Hi, check out "this book ". Its cool
I was originally using replaceOccurencesOfString ""\" with "". But then it will do the following:
Hi, check out \this book \. Its cool
So, I concluded I need the above regular expression to match only " but not \" and then replace only those double quotes.
thanks
mbh
[^\\]\"
[^m] means does not match m
Not sure how this might translate to whatever is supported in the iOS apis, but, if they support anchoring (which I think all regex engines should), you're describing something like
(^|[^\])"
That is, match :
either the beginning of the string ^ or any character that's not
\ followed by:
the " character
If you want to do any sort of replacement, you'll have to grab the first (and only) group in the regex (that is the parenthetically grouped part of the expression) and use it in the replacement. Often this value labeled as $1 or \1 or something like that in your replacement string.
If the regex engine is PCRE based, of course you could put the grouped expression in a lookbehind so you wouldn't need to capture and save the capture in the replacement.
Not sure about regex, a simpler solution is,
NSString *str = #"\"Hi, check out \\\"this book \\\". Its cool\"";
NSLog(#"string before modification = %#", str);
str = [str stringByReplacingOccurrencesOfString:#"\\\"" withString:#"#$%$#"];
str = [str stringByReplacingOccurrencesOfString:#"\"" withString:#""];
str = [str stringByReplacingOccurrencesOfString:#"#$%$#" withString:#"\\\""];//assuming that the chances of having '#$%$#' in your string is zero, or else use more complicated word
NSLog(#"string after modification = %#", str);
Output:
string before modification = "Hi, check out \"this book \". Its cool"
string after modification = Hi, check out \"this book \". Its cool
Regex: [^\"].*[^\"]. which gives, Hi, check out \"this book \". Its cool
It looks like it's a JSON string? Perhaps created using json_encode() in PHP on the server? You should use the proper JSON parser in iOS. Don't use regex as you will run into bugs.
// fetch the data, eg this might return "Hi, check out \"this book \". Its cool"
NSData *data = [NSData dataWithContentsOfURL:#"http://example.com/foobar/"];
// decode the JSON string
NSError *error;
NSString *responseString = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
// check if it worked or not
if (!responseString || ![responseString isKindOfClass:[NSString class]]) {
NSLog(#"failed to decode server response. error: %#", error);
return;
}
// print it out
NSLog(#"decoded response: %#", responseString);
The output will be:
Hi, check out "this book ". Its cool
Note: the JSON decoding API accepts an NSData object, not an NSString object. I'm assuming you also have a data object and are converting it to a string at some point... but if you're not, you can convert NSString to NSData using:
NSString *responseString = [NSJSONSerialization JSONObjectWithData:[myString dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:&error];
More details about JSON can be found at:
http://www.json.org
http://developer.apple.com/library/ios/#documentation/Foundation/Reference/NSJSONSerialization_Class/Reference/Reference.html

Test If An NSString Contains a Letter

I'm working on an iPhone project and I need to check if the user's input in a UITextfield contains a letter. More generally if an NSString contains a letter.
I tried this with a giant if loop with the rangeofstring:#"A".location == NSNotFound and then did OR rangeofstring:#"B".location == NSNotFound
and so on....
But:
It doesn't seem to work
There has to be a simple line of code to check if the NSString contains letters.
I have been searching this for hours... Can someone please answer this question???
Use an NSCharacterSet. Note that letterCharacterSet includes all things that are "letters" or "ideographs." So that includes é and 中, but usually that's what you want. If you want a specific set of letters (like English letters), you can construct your own NSCharacterSet with them using characterSetWithCharactersInString:.
if ([string rangeOfCharacterFromSet:[NSCharacterSet letterCharacterSet]].location == NSNotFound)
If you want to make sure the text has a certain letter in it (as opposed to just ANY letter), use the rangeOfString: message. For example, to ensure the text contains the letter "Q":
NSString *string = #"poQduu";
if ([string rangeOfString:#"Q"].location != NSNotFound) {
DLog (#"Yes, we have a Q at location %i", [string rangeOfString:#"Q"].location );
}
As others (Rob Napier) note, if you want to find ANY letter, use the rangeOfCharacterFromSet: message.
if ([string rangeOfCharacterFromSet:[NSCharacterSet letterCharacterSet]].location != NSNotFound) ...

How to check if NSString is contains a numeric value?

I have a string that is being generate from a formula, however I only want to use the string as long as all of its characters are numeric, if not that I want to do something different for instance display an error message.
I have been having a look round but am finding it hard to find anything that works along the lines of what I am wanting to do. I have looked at NSScanner but I am not sure if its checking the whole string and then I am not actually sure how to check if these characters are numeric
- (void)isNumeric:(NSString *)code{
NSScanner *ns = [NSScanner scannerWithString:code];
if ( [ns scanFloat:NULL] ) //what can I use instead of NULL?
{
NSLog(#"INSIDE IF");
}
else {
NSLog(#"OUTSIDE IF");
}
}
So after a few more hours searching I have stumbled across an implementation that dose exactly what I am looking for.
so if you are looking to check if their are any alphanumeric characters in your NSString this works here
-(bool) isNumeric:(NSString*) hexText
{
NSNumberFormatter* numberFormatter = [[[NSNumberFormatter alloc] init] autorelease];
NSNumber* number = [numberFormatter numberFromString:hexText];
if (number != nil) {
NSLog(#"%# is numeric", hexText);
//do some stuff here
return true;
}
NSLog(#"%# is not numeric", hexText);
//or do some more stuff here
return false;
}
hope this helps.
Something like this would work:
#interface NSString (usefull_stuff)
- (BOOL) isAllDigits;
#end
#implementation NSString (usefull_stuff)
- (BOOL) isAllDigits
{
NSCharacterSet* nonNumbers = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
NSRange r = [self rangeOfCharacterFromSet: nonNumbers];
return r.location == NSNotFound && self.length > 0;
}
#end
then just use it like this:
NSString* hasOtherStuff = #"234 other stuff";
NSString* digitsOnly = #"123345999996665003030303030";
BOOL b1 = [hasOtherStuff isAllDigits];
BOOL b2 = [digitsOnly isAllDigits];
You don't have to wrap the functionality in a private category extension like this, but it sure makes it easy to reuse..
I like this solution better than the others since it wont ever overflow some int/float that is being scanned via NSScanner - the number of digits can be pretty much any length.
Consider NSString integerValue - it returns an NSInteger. However, it will accept some strings that are not entirely numeric and does not provide a mechanism to determine strings which are not numeric at all. This may or may not be acceptable.
For instance, " 13 " -> 13, "42foo" -> 42 and "helloworld" -> 0.
Happy coding.
Now, since the above was sort of a tangent to the question, see determine if string is numeric. Code taken from link, with comments added:
BOOL isNumeric(NSString *s)
{
NSScanner *sc = [NSScanner scannerWithString: s];
// We can pass NULL because we don't actually need the value to test
// for if the string is numeric. This is allowable.
if ( [sc scanFloat:NULL] )
{
// Ensure nothing left in scanner so that "42foo" is not accepted.
// ("42" would be consumed by scanFloat above leaving "foo".)
return [sc isAtEnd];
}
// Couldn't even scan a float :(
return NO;
}
The above works with just scanFloat -- e.g. no scanInt -- because the range of a float is much larger than that of an integer (even a 64-bit integer).
This function checks for "totally numeric" and will accept "42" and "0.13E2" but reject " 13 ", "42foo" and "helloworld".
It's very simple.
+ (BOOL)isStringNumeric:(NSString *)text
{
NSCharacterSet *alphaNums = [NSCharacterSet decimalDigitCharacterSet];
NSCharacterSet *inStringSet = [NSCharacterSet characterSetWithCharactersInString:text];
return [alphaNums isSupersetOfSet:inStringSet];
}
Like this:
- (void)isNumeric:(NSString *)code{
NSScanner *ns = [NSScanner scannerWithString:code];
float the_value;
if ( [ns scanFloat:&the_value] )
{
NSLog(#"INSIDE IF");
// do something with `the_value` if you like
}
else {
NSLog(#"OUTSIDE IF");
}
}
Faced same problem in Swift.
In Swift you should use this code, according TomSwift's answer:
func isAllDigits(str: String) -> Bool {
let nonNumbers = NSCharacterSet.decimalDigitCharacterSet()
if let range = str.rangeOfCharacterFromSet(nonNumbers) {
return true
}
else {
return false
}
}
P.S. Also you can use other NSCharacterSets or their combinations to check your string!
For simple numbers like "12234" or "231231.23123" the answer can be simple.
There is a transformation law for int numbers: when string with integer transforms to int (or long) number and then, again, transforms it back to another string these strings will be equal.
In Objective C it will looks like:
NSString *numStr=#"1234",*num2Str=nil;
num2Str=[NSString stringWithFormat:#"%lld",numStr.longlongValue];
if([numStr isEqualToString: num2Str]) NSLog(#"numStr is an integer number!");
By using this transformation law we can create solution
to detect double or long numbers:
NSString *numStr=#"12134.343"
NSArray *numList=[numStr componentsSeparatedByString:#"."];
if([[NSString stringWithFormat:#"%lld", numStr.longLongValue] isEqualToString:numStr]) NSLog(#"numStr is an integer number");
else
if( numList.count==2 &&
[[NSString stringWithFormat:#"%lld",((NSString*)numList[0]).longLongValue] isEqualToString:(NSString*)numList[0]] &&
[[NSString stringWithFormat:#"%lld",((NSString*)numList[1]).longLongValue] isEqualToString:(NSString*)numList[1]] )
NSLog(#"numStr is a double number");
else
NSLog(#"numStr is not a number");
I did not copy the code above from my work code so can be some mistakes, but I think the main point is clear.
Of course this solution doesn't work with numbers like "1E100", as well it doesn't take in account size of integer and fractional part. By using the law described above you can do whatever number detection you need.
C.Johns' answer is wrong. If you use a formatter, you risk apple changing their codebase at some point and having the formatter spit out a partial result. Tom's answer is wrong too. If you use the rangeOfCharacterFromSet method and check for NSNotFound, it'll register a true if the string contains even one number. Similarly, other answers in this thread suggest using the Integer value method. That is also wrong because it will register a true if even one integer is present in the string. The OP asked for an answer that ensures the entire string is numerical. Try this:
NSCharacterSet *searchSet = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
Tom was right about this part. That step gives you the non-numerical string characters. But then we do this:
NSString *trimmedString = [string stringByTrimmingCharactersInSet:searchSet];
return (string.length == trimmedString.length);
Tom's inverted character set can TRIM a string. So we can use that trim method to test if any non numerals exist in the string by comparing their lengths.

Validate NSString

I am validating an NSString to ensure that the string does not contain apostrophes.
The code I'm using to do this is
NSCharacterSet * invalidNumberSet = [NSCharacterSet characterSetWithCharactersInString:#"'"];
NSScanner * scanner = [NSScanner scannerWithString:string];
NSString * scannerResult;
[scanner setCharactersToBeSkipped:nil];
[scanner scanUpToCharactersFromSet:invalidNumberSet intoString:&scannerResult];
if(![string isEqualToString:scannerResult])
{
return 2;
}
Returning 2 represents an error. This code works, except for the case where the string is an apostrophe.
To get around this issue, I added the following code above the preceding block.
if([string isEqualToString:#"'"]);
{
return 2;
}
This code is evaluating to true, regardless of the input. I need to either prevent the first block from crashing with the input of ', or get the second block to work.
What am I missing?
There's no logical reason why the isEqualToString: test should always succeed. If that's your actual, copy-pasted code, you must have an error somewhere else in the function.
At any rate, it would be much simpler to test if the location of [string rangeOfString:#"'"] is NSNotFound.