I have a block of text that have to be parsed. It is kind a template like
"Dear $name, we need the registration number of your $vehicle, bla bla"...
imagine this 1000 characters long, with a lot of key variables, like $name, $vehicle, etc.
This text is stored on a #define
At run time, I have to parse this template and other 20 like that, replacing the key variables with the real values, like "Dear John, ....".
I was using a NSString variable to store the initial text and then these lines
NSString *start = TEMPLATE1;
start = [start stringByReplacingOccurrencesOfString:NAME withString:realName];
start = [start stringByReplacingOccurrencesOfString:VEHICLE withString:realVehicle];
and so one and the code is working fast and well, but someone suggested using a NSMutableString for the start variable, as it would use less memory.
Is this correct?
Will it worth the change?
It would be reasonable to do this:
NSMutableString *text = [NSMutableString stringWithString:TEMPLATE1];
[text replaceOccurrencesOfString:NAME withString:realName options:0 range:NSMakeRange(0, [text length])];
[text replaceOccurrencesOfString:VEHICLE withString:realVehicle options:0 range:NSMakeRange(0, [text length])];
But if your code is already "working fast and well", I wouldn't bother changing it.
Related
I have a string like:
<book>MyBook</book><value>myValue</value>
Now I want to get the text "myValue" out of this string. I want to use NSRegularExpression to do this. I tried this:
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"(<book>MyBook</book>\\s*<value>).*?(</value>)"
options:NSRegularExpressionCaseInsensitive
error:&error];
NSArray *textArray = [regex matchesInString:myData options:0 range:NSMakeRange(0, [myData length])];
NSTextCheckingResult * result = [rege firstMatchInString:myData
options:0
range:NSMakeRange(0, [myData length])];
The result is:
<book>MyBook</book><value>myValue</value>
So I get the whole string, but I only want "myValue". How can I do this? What am I missing here?
Thanks in advance!
That happens because you wrote a regex that matches the entire string. I'd reckon that writing a regex that will only match the myValue part of the string is way too complicated to be bothered with (due to the fact that you've got MyBook string that will probably match anything myValue does).
I'd recommend not using regex for this, as they are not intended for the use you've described here. If you don't want to use any XML deserialization, you could use a NSScanner or any of the NSString class methods which will yield a simpler, and easier code to maintain.
For example, using an NSScanner and a few other methods:
NSString *stringToBeScanned = #"<book>MyBook</book><value>myValue</value>";
NSString *myValue;
NSScanner *scanner = [NSScanner scannerWithString:stringToBeScanned];
[scanner scanUpToString:#"<value>" intoString:nil];
// After the above, we've got "<value>myValue</value>" left to scan
[scanner scanUpToString:#"</value>" intoString:&myValue];
// We ended up with a "<value>myValue" type of a string
// This will trim the remaining of the string we don't need
myValue = [myValue stringByReplacingOccurrencesOfString:#"<value>" withString:#""];
The above could probably be written better and I might have made a mistake or two writing it out my head, but the principle should work.
If I take a following question. What is the best way to initialize NSMutableString Class?
(All instance will be return at unexpected times... so I'll assume that the initialization as follows:)
If I know in advance the amount of work. ( expected )
NSMutableString *str1 = [NSMutableString stringWithString:#""];
NSMutableString *str2 = [NSMutableString stringWithCapacity:0];
NSMutableString *str3 = [NSMutableString stringWithCapacity:1000];
NSMutableString *str4 = [NSMutableString string];
for(int i = 0; i< 1000; i++)
{
//The following tasks.(Adding to the string to continue working.)
[/*str1~str4*/ appendFormat:#"%d", i];
}
If I don't know in advance the amount of work. ( unexpected )
NSMutableString *str1 = [NSMutableString stringWithString:#""];
NSMutableString *str2 = [NSMutableString stringWithCapacity:0];
NSMutableString *str3 = [NSMutableString stringWithCapacity:1000];
NSMutableString *str4 = [NSMutableString string];
for(int i = 0; i< /*a large of size(unpredictable)*/ ; i++)
{
//The following tasks.(Adding to the string to continue working.)
[/*str1~str4*/ appendFormat:#"%d", i];
}
Largely split into two when performing these tasks, What is the best way to initialize?
I sometimes when working with these task is also confusing.
Case 1
Of the options listed, I'd use:
NSMutableString *str3 = [NSMutableString stringWithCapacity:1000];
…if you know the destination size, or estimate it with a little room at the top and are able to quickly determine the exact size, or the size worst case scenario, this could save multiple reallocate and copy operations. If you don't know the size in the worst case scenario, or if it takes a lot of time to calculate, then you may as well use [NSMutableString string] or [NSMutableString new]. Also, *WithCapacity is a hint, which the frameworks are free to ignore.
Of course, the body of your loop and the size you reserve also implies that all the values are [0…9] (specifically, that all values consume one character), and you could in that case likely do far better by using format strings with more arguments. However, i is obviously larger than 9 for most iterations, and will consume on average 3 characters each, so 3000 would be a more appropriate reserve capacity for the exact code you posted.
Case 2
Of the options listed, I'd use:
NSMutableString *str4 = [NSMutableString string];
Even better, if you don't need to add it to an autorelease pool: [NSMutableString new] or [[NSMutableString alloc] init].
Other Notes
Yes, keeping objects out of autorelease pools (e.g. use alloc+init) can improve performance and reduce peak memory usage significantly. Sometimes, this is beyond your control, and in some environments (e.g. ARC), this may happen even though you use an autoreleased convenience constructor - e.g. [NSMutableString string].
The Faster Solution
Finally, if this case you have outlined really is a performance concern, the fastest way would be to create a char buffer on the stack and then create one NSString from the result of copying the numbers over to the char buffer. Assuming your ints are all 0-9, it would be very fast and easy, then simply create an NSString from the (terminated) cstring. You can even do this if the input size varies, or is very large (results in a very long string).
It doesn't really matter.
If you've optimized your program so far that this decision will have a measurable effect on its overall performance, pat yourself on the back or, as Sheldon from BBT would say, "have a chocolate!"
PS:
If you precisely know the size up front or have a really good estimate on it, then use that size in stringWithCapacity: or initWithCapacity: if you don't, then don't even bother — let the framework decide, it's pretty damn clever!
Here is the shortest way:
NSMutableString *string = [#"" mutableCopy];
NSMutableString *str1 = [NSMutableString stringWithString:#""];
Bad, a NSString (#"") is pointlessly created, then copied into a new NSMutable String
NSMutableString *str2 = [NSMutableString stringWithCapacity:0];
Bad, a NSMutableString with 0 capacity will need to be inflated upon the first addition to it.
NSMutableString *str3 = [NSMutableString stringWithCapacity:1000];
Good, if 1000 has some significance (i.e. it's an expected size for the content you're working with).
NSMutableString *str4 = [NSMutableString string];
Good, using a convenience method, which is is like doing [NSMutableString alloc] init];
If you know the size upfront, use
[NSMutableString stringWithCapacity: _Known_Size_];
If you don't know the size up front, use
[NSMutableString stringWithCapacity: _small_number_];
Then the mutable string will grow as it needs to.
In Swift 5:
var mutableString = NSMutableString("")
It is always better to allocate and initialize an object, not use a class method to create it (which puts it in the closest autorelease pool).
So,
NSMutableString* stringOne = [[NSMutableString alloc] initWithCapacity:capacity];
instead of
NSMutableString* stringTwo = [NSMutableString stringWithCapacity:capacity];
Just remember that you have to release stringOne when you are done with it, but not stringTwo, because the stringWithCapacity: class method returns an autoreleased object.
Read more about the topic here: http://www.mulle-kybernetik.com/artikel/Optimization/opti-5.html
> (2009 RX7)</font></td>
>monospace" size="-1">214869 (2007 PAZ)</font></td>
>monospace" size="-1"> 4155 Accord</font></td>
I wonder if someone could offer me a little help, I have a list of NSString items (See Above) that I want to parse some data from. My problem is that there are no tags that I can use within the strings nor do the items I want have fixed positions. The data I want to extract is:
2009 RX7
2007 PAZ
4155 Accord
My thinking is that its going to be easier to parse from the right hand end, remove the </font></td> and then use ";" to separate the data items:
(2009  RX7)
(2007  PAZ)
4155  Accord
which can them be cleaned up to match the example given. Any pointers on doing this or working through from the right would be very much appreciated.
Personally I think you are better off with a regex. So my solution would be:
Regex of: ([0-9]+)[^;]+;([A-Za-z0-9]+)
Which for all the example text provides 3 matches. ie for:
(2009 RX7)</font></td>
0: 2009 RX7)<
1: 2009
2: RX7
I haven't coded this up, but did test the Regex at www.regextester.com
Regex's are implemented via NSRegularExpression and are available in iOS 4.0 and later.
Edit
Given that this appears to be a web scraping application, you never know when those pesky HTML code monkeys will change their output and break your carefully crafted matching methodology. As such I would change my regex to:
([0-9]+)([^;]+;)+([A-Za-z0-9]+)
Which adds an extra group, but allows for any number of elements between the number and the string.
Try this code:
NSString *str = #"> (2009 RX7)</font></td>";
NSRange fontRange = [str rangeOfString:#"</Font>" options:NSBackwardsSearch];
NSRange lastSemi = [str rangeOfString:#";" options:NSBackwardsSearch range:NSMakeRange(0, fontRange.location-1)];
NSRange priorSemi = [str rangeOfString:#";" options:NSBackwardsSearch range:NSMakeRange(0, lastSemi.location-1)];
NSString *yourString = [str substringWithRange:NSMakeRange(priorSemi.location+1, fontRange.location-1)];
The key element here is the NSBackwardsSearch search option.
This should do the trick:
NSString *s = #">monospace\" size=\"-1\"> 4155 Accord</font></td>";
NSArray *strArray = [s componentsSeparatedByString:#";"];
// you're interested in last two objects
NSArray *tmp = [strArray subarrayWithRange:NSMakeRange(strArray.count - 2, 2)];
In tmp you'll have something like:
"4155 ",
"Accord</font></td>"
strip unneeded chars and you're all set.
Using NSRegularExpression:
NSRegularExpression *regex;
NSTextCheckingResult *match;
NSString *pattern = #"([0-9]+) ([A-Za-z0-9]+)[)]?</font></td>";
NSString *string = #"> (2009 RX7)</font></td>";
regex = [NSRegularExpression
regularExpressionWithPattern:pattern
options:NSRegularExpressionCaseInsensitive
error:nil];
match = [regex firstMatchInString:string options:0 range:NSMakeRange(0, [string length])];
NSLog(#"'%#'", [string substringWithRange:[match rangeAtIndex:1]]);
NSLog(#"'%#'", [string substringWithRange:[match rangeAtIndex:2]]);
NSLog output:
'2009'
'RX7'
I would like to know how to selectively trim an NSMutableString. For example, if my string is "MobileSafari_2011-09-10-155814_Jareds-iPhone.plist", how would I programatically trim off everything except the word "MobileSafari"?
Note : Given the term programatically above, I expect the solution to work even if the word "MobileSafari" is changed to "Youtube" for example, or the word "Jared's-iPhone" is changed to "Angela's-iPhone".
Any help is very much appreciated!
Given that you always need to extract the character upto the first underscore; use the following method;
NSArray *stringParts = [yourString componentsSeparatedByString:#"_"];
The first object in the array would be the extracted part you need I would think.
TESTED CODE: 100% WORKS
NSString *inputString=#"MobileSafari_2011-09-10-155814_Jareds-iPhone.plist";
NSArray *array= [inputString componentsSeparatedByString:#"_"];
if ([array count]>0) {
NSString *resultedString=[array objectAtIndex:0];
NSLog(#" resultedString IS - %#",resultedString);
}
OUTPUT:
resultedString IS - MobileSafari
If you know the format of the string is always like that, it can be easy.
Just use NSString's componentsSeparatedByString: documented here.
In your case you could do this:
NSString *source = #"MobileSafari_2011-09-10-155814_Jareds-iPhone.plist";
NSArray *seperatedSubStrings = [source componentsSeparatedByString:#"_"];
NSString *result = [seperatedSubStrings objectAtIndex:0];
#"MobileSafari" would be at index 0, #"2011-09-10-155814" at index 1, and #"Jareds-iPhone.plist" and at index 2.
Try this :
NSString *strComplete = #"MobileSafari_2011-09-10-155814_Jareds-iPhone.plist";
NSArray *arr = [strComplete componentsSeparatedByString:#"_"];
NSString *str1 = [arr objectAtIndex:0];
NSString *str2 = [arr objectAtIndex:1];
NSString *str3 = [arr objectAtIndex:2];
str1 is the required string.
Even if you change MobileSafari to youtube it will work.
So you'll need an NSString variable that'll hold the beginning of the string you want to truncate. After that one way could be to change the string and the variable string values at the simultanously. Say, teh Variable string was "Youtube" not it is changed to "MobileSafari" then the mutable string string should change from "MobileSafari_....." to "YouTube_......". And then you can get the variable strings length and used the following code to truncate the the mutable string.
NSString *beginningOfTheStr;
.....
theMutableStr=[theMutableStr substringToIndex:[beginningOfTheStrlength-1]];
See if tis works for you.
Is their a method to encode/decode HTML and URL (in Xcode, using Objective-C)?
[NSString stringWithContentsOfFile:<#(NSString *)path#> encoding:<#(NSStringEncoding)enc#> error:<#(NSError **)error#>]
This doesn't seem to work how i expected. I thought it will convert special characters like "<" to equivalent HTML entities i.e. "<" in this case.
Here's a reference to the w3school link related to this topic (general):
HTML URL Encoding Reference
HTML Entities Reference
Thanking in anticipation.
Returns a representation of the receiver using a given encoding to determine the percent escapes necessary to convert the receiver into a legal URL string.
- (NSString *)stringByAddingPercentEscapesUsingEncoding:(NSStringEncoding)encoding
and
Returns a new string made by replacing in the receiver all percent escapes with the matching characters as determined by a given encoding.
- (NSString *)stringByReplacingPercentEscapesUsingEncoding:(NSStringEncoding)encoding
The method you cite reads a file from disk with a given character encoding (such as UTF-8 or ASCII). It has nothing to do with URL or HTML escaping.
If you want to add URL percent escapes, you want this method:
[myString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
Make sure you read the documentation about this method, because there are certain subtleties about what it escapes and what it leaves alone. In some cases, you may have to use the more complex, but more flexible, CFURLCreateStringByAddingPercentEscapes(). (If you do, note that you can cast CFStringRef to NSString * and vice versa.)
There's nothing built in that I know of to do XML/HTML-style entity escaping, but this function ought to handle the basics:
NSString * convertToXMLEntities(NSString * myString) {
NSMutableString * temp = [myString mutableCopy];
[temp replaceOccurrencesOfString:#"&"
withString:#"&"
options:0
range:NSMakeRange(0, [temp length])];
[temp replaceOccurrencesOfString:#"<"
withString:#"<"
options:0
range:NSMakeRange(0, [temp length])];
[temp replaceOccurrencesOfString:#">"
withString:#">"
options:0
range:NSMakeRange(0, [temp length])];
[temp replaceOccurrencesOfString:#"\""
withString:#"""
options:0
range:NSMakeRange(0, [temp length])];
[temp replaceOccurrencesOfString:#"'"
withString:#"'"
options:0
range:NSMakeRange(0, [temp length])];
return [temp autorelease];
}
To do HTML/XML entity encoding, you can use a CFMutableString function:
NSString *result = .....;
CFStringTransform((CFMutableStringRef)result, NULL, kCFStringTransformToXMLHex, false);
By setting the last parameter of CFStringTransform to true, it should work for decoding (hex) entities as well.
Use CFStringTransform for HTML entity encoding/decoding:
CFStringTransform((CFTypeRef)yourMutableString, NULL, CFSTR("Any-Hex/XML"), FALSE );
You need to use the ICU transform "Any-Hex/XML". kCFStringTransformToXMLHex isn't aggressive enough.