I have a series of characters that I want to match with a regular expression, and replace them with specific strings depending on what they are.
Example:
In => "This is the input string where i want to replace 1 2 & 3"
Out => "This is the input string where i want to replace ONE TWO & THREE"
I currently do this by splitting the string using spaces as the separator, and parsing each string individually, incrementally rebuilding the string. I feel this is ugly, and lacking imagination, and kinda slow.
According the Apple documentation I should be able to do this using the replacementStringForResult:inString:offset:template: method. However I can't seem to understand how to use it correctly.
You can use the method within a for in loop using matchesInString:options:range:, which returns an array of matches as NSTextCheckingResults:
NSError* error = NULL;
NSRegularExpression* regex = [NSRegularExpression
regularExpressionWithPattern:#"\\b[1-3]\\b"
options:NSRegularExpressionCaseInsensitive
error:&error];
NSString* yourString = #"This is the input string where i want to replace 1 2 & 3";
NSMutableString* mutableString = [yourString mutableCopy];
NSInteger offset = 0; // keeps track of range changes in the string
// due to replacements.
for (NSTextCheckingResult* result in [regex matchesInString:yourString
options:0
range:NSMakeRange(0, [yourString length])]) {
NSRange resultRange = [result range];
resultRange.location += offset; // resultRange.location is updated
// based on the offset updated below
// implement your own replace functionality using
// replacementStringForResult:inString:offset:template:
// note that in the template $0 is replaced by the match
NSString* match = [regex replacementStringForResult:result
inString:mutableString
offset:offset
template:#"$0"];
NSString* replacement;
if ([match isEqualToString:#"1"]) {
replacement = #"ONE";
} else if ([match isEqualToString:#"2"]) {
replacement = #"TWO";
} else if ([match isEqualToString:#"3"]) {
replacement = #"THREE";
}
// make the replacement
[mutableString replaceCharactersInRange:resultRange withString:replacement];
// update the offset based on the replacement
offset += ([replacement length] - resultRange.length);
}
NSLog(#"mutableString: %#", mutableString); // mutableString: This is the input string where i want to replace ONE TWO & THREE
The answer of Dano works perfectly and following the idea of Pedro in the comments I wrapped the code into a category that takes a block that does the replacement part.
This is very handy to use.
NSRegularExpression+Replacement.h
#interface NSRegularExpression (Replacement)
- (NSString *)stringByReplacingMatchesInString:(NSString *)string
options:(NSMatchingOptions)options
range:(NSRange)range
template:(NSString *)templ
withMatchTransformation: (NSString* (^) (NSString* element))transformation;
#end
NSRegularExpression+Replacement.m
#implementation NSRegularExpression (Replacement)
- (NSString *)stringByReplacingMatchesInString:(NSString *)string
options:(NSMatchingOptions)options
range:(NSRange)range
template:(NSString *)templ
withMatchTransformation: (NSString* (^) (NSString* element))transformation
{
NSMutableString* mutableString = [string mutableCopy];
NSInteger offset = 0; // keeps track of range changes in the string due to replacements.
for (NSTextCheckingResult* result in [self matchesInString:string
options:0
range:range])
{
NSRange resultRange = [result range];
resultRange.location += offset; // resultRange.location is updated based on the offset updated below
// implement your own replace functionality using
// replacementStringForResult:inString:offset:template:
// note that in the template $0 is replaced by the match
NSString* match = [self replacementStringForResult:result
inString:mutableString
offset:offset
template:templ];
// get the replacement from the provided block
NSString *replacement = transformation(match);
// make the replacement
[mutableString replaceCharactersInRange:resultRange withString:replacement];
// update the offset based on the replacement
offset += ([replacement length] - resultRange.length);
}
return mutableString;
}
#end
And here is how you use it to solve the initial question:
NSString* yourString = #"This is the input string where i want to replace 1 2 & 3";
NSError* error = nil;
NSRegularExpression* regex = [NSRegularExpression
regularExpressionWithPattern:#"\\b[1-3]\\b"
options:0
error:&error];
return [regex stringByReplacingMatchesInString:yourString options:0 range:NSMakeRange(0, [yourString length]) template:#"$0" withMatchTransformation:^NSString *(NSString *match) {
NSString* replacement;
if ([match isEqualToString:#"1"]) {
replacement = #"ONE";
} else if ([match isEqualToString:#"2"]) {
replacement = #"TWO";
} else if ([match isEqualToString:#"3"]) {
replacement = #"THREE";
} else {
return replacement;
}
}];
You should use
- (NSString *)stringByReplacingMatchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range withTemplate:(NSString *)template
or
- (NSUInteger)replaceMatchesInString:(NSMutableString *)string options:(NSMatchingOptions)options range:(NSRange)range withTemplate:(NSString *)template
with string being "This is the input string where i want to replace 1 2 & 3" and template being either "ONE", "TWO" or "THREE".
I needed a more generic solution for the same problem so I refined Dano's answer to a method like this, with a sample usage explained below:
- (NSMutableString *)replaceSubstringsInString:(NSString*)string
usingRegex:(NSString*)searchRegex
withReplacements:(NSDictionary*)replacements {
NSMutableString *newString = [string mutableCopy];
__block NSInteger offset = 0;
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression
regularExpressionWithPattern:searchRegex
options:0
error:&error];
[regex enumerateMatchesInString:string
options:0
range:NSMakeRange(0, [string length])
usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop){
NSRange resultRange = [match range];
resultRange.location += offset;
NSString *substring = [string substringWithRange:match.range];
__block NSString* replacement;
[replacements enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([key isEqualToString:substring]) {
replacement = obj;
}
}];
[newString replaceCharactersInRange:resultRange withString:replacement];
offset += ([replacement length] - resultRange.length);
}];
return newString;
}
Usage:
NSString *string = #"This is the input string where i want to replace 1 2 & 3";
NSString *searchRegex = #"\\b[1-3]\\b";
NSDictionary *replacements = #{#"1":#"ONE",#"2":#"TWO",#"3":#"THREE"};
NSMutableString *result = [self replaceSubstringsInString:string
usingRegex:searchRegex
withReplacements:replacements];
Explanation:
You just have to pass in the string to search for the matching substrings, along with the desired regexSearch pattern and a replacements dictionary containing the string pairs with the key being the substring to be replaced and the object with the desired replacement string.
Output:
// This is the input string where i want to replace ONE TWO & THREE
I was looking for something similar, but didn't like most of the answers here, so I wrote something inspired by how PHP does string replacement:
#implementation NSString (preg_replace)
- (instancetype)stringByReplacingMatchesFromRegularExpression:(NSRegularExpression *)regularExpression replacementBlock:(NSString * (^)(NSArray *matches))replacementBlock
{
NSMutableString *finalString = self.mutableCopy;
NSUInteger offset = 0;
for (NSTextCheckingResult *match in [regularExpression matchesInString:self options:0 range:NSMakeRange(0, self.length)]) {
NSMutableArray *matches = [NSMutableArray array];
for (NSUInteger index = 0; index < match.numberOfRanges; ++index) {
[matches addObject:[self substringWithRange:[match rangeAtIndex:index]]];
}
NSString *replacementString = replacementBlock(matches.copy);
[finalString replaceCharactersInRange:NSMakeRange(match.range.location + offset, match.range.length) withString:replacementString];
offset += replacementString.length - match.range.length;
}
return finalString;
}
#end
To use it:
NSRegularExpression *expression = [[NSRegularExpression alloc] initWithPattern:#"[0-9A-F]{2}" options:0 error:nil];
NSString *string = #"AB-DE-EF";
NSString *result = [string stringByReplacingMatchesFromRegularExpression:expression replacementBlock:^NSString *(NSArray *matches) {
return [NSString stringWithFormat:#"(%#)", [matches[0] lowercaseString]];
}];
NSLog(#"Result = %#", result); // "(ab)-(de)-(ef)"
I recommend this, much shorter and uses a little memory :
Another solution
Related
I need to replace a word like DFT with the registered trademark symbol everywhere in a string from a plist.
My code works but always messes up the second one in the same string:
- (NSAttributedString *)addRegisteredTrademarkTo:(NSString*)text
{
NSMutableAttributedString *rawAttString=[[NSMutableAttributedString alloc] initWithString:text];
UIFont *boldFont = [UIFont boldSystemFontOfSize:8];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:#"®"
attributes:#{NSFontAttributeName:boldFont,
NSForegroundColorAttributeName:[UIColor blackColor],
NSBaselineOffsetAttributeName : #4,
NSFontAttributeName:[UIFont fontWithName:#"Helvetica" size:5]
}];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"DRT" options:kNilOptions error:nil];
NSRange range = NSMakeRange(0,rawAttString.length);
[regex enumerateMatchesInString:text options:kNilOptions range:range usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSRange subStringRange = [result rangeAtIndex:0];
[rawAttString replaceCharactersInRange:subStringRange withAttributedString:attributedString];
}];
NSAttributedString *t = [[NSAttributedString alloc] initWithAttributedString:rawAttString];
return t;
}
String is:Fake name™ with Another FakeDRT building access. SionDRT elevators size
Output is:
The second symbol is always misplaced - and I can't figure out why. Any pointers?
Basically, my string looks like this:
#"{{attendee.prefix}} {{attendee.firstname}} {{attendee.lastname}}, fwf<br /><span style="font-size:14px;">lalallasgabab {{attendee.weg2g}} {{attendee.5236t2gsg}} {{attendee.ticket_no}} agagawfbeagabs</span>"
I am trying to extract all the string which encapsulated by 2 curly braces:
[ {{attendee.prefix}}, {{attendee.firstname}}, {{attendee.lastname}}, {{attendee.weg2g}}, {{attendee.5236t2gsg}}, {{attendee.ticket_no}} ]
I have tried these regex, but it always return 1 match if not the whole string.
#"(\\{\\{.*\\}\\})" -> return the whole string
#"\\{\\{[^}]*+\\}\\}" -> only match {{attendee.firstname}}
#"\\b\\{\\{[^}]*+\\}\\}\\b" -> only match {{attendee.prefix}}
Here is my code:
NSString *myString = #"{{attendee.prefix}} {{attendee.firstname}} {{attendee.lastname}}, fwf<br /><span style="font-size:14px;">lalallasgabab {{attendee.weg2g}} {{attendee.5236t2gsg}} {{attendee.ticket_no}} agagawfbeagabs</span>"
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"\\{\\{[^}]*+\\}\\}" options:NSRegularExpressionCaseInsensitive error:nil];
NSRange visibleTextRange = NSMakeRange(0, myString.length);
NSArray *matches = [regex matchesInString:myString options:NSMatchingAnchored range:visibleTextRange];
for (NSTextCheckingResult *match in matches)
{
NSLog(#"%#: Match - %#", [self class], [myString substringWithRange:match.range]);
}
I have tried using [match rangeAtIndex:index] but still return the same thing, sometime it is out of bound because the match result is only 1.
Appreciate any help here. Thanks.
PS: I am new to Objective-C and RegEx, so pardon this question.
To iterate over all matches like {{this}}, use this:
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"\\{\\{[^}]*\\}\\}" options:0 error:&error];
NSArray *matches = [regex matchesInString:subject options:0 range:NSMakeRange(0, [subject length])];
NSUInteger matchCount = [matches count];
if (matchCount) {
for (NSUInteger matchIdx = 0; matchIdx < matchCount; matchIdx++) {
NSTextCheckingResult *match = [matches objectAtIndex:matchIdx];
NSRange matchRange = [match range];
NSString *result = [subject substringWithRange:matchRange];
}
}
else { // Nah... No matches.
}
I managed to answer my own question, by using different ways of enumerating the result:
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"\\{\\{[^}]*+\\}\\}" options:NSRegularExpressionCaseInsensitive error:nil];
NSString *myString = #"{{attendee.prefix}} {{attendee.firstname}} {{attendee.lastname}}, fwf<br /><span style='font-size:14px;'>lalallasgabab {{attendee.weg2g}} {{attendee.5236t2gsg}} {{attendee.ticket_no}} agagawfbeagabs</span>";
[regex enumerateMatchesInString:myString
options:0
range:NSMakeRange(0, [myString length])
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSLog(#"%#: Match - %#", [self class], [self.duplicateBody substringWithRange:result.range]);
}];
With the code above, I am able to iterate through each of the matched string, which is exactly what I wanted.
I have this string (2.5+1)*5
I want to make a NSMutableArray containing each of the elements:
[(, 2.5, +, 1, ), *, 5]
This is the code I wrote:
+ (NSMutableArray*) convertToList: (NSString *) exp {
NSRegularExpression * reg = [NSRegularExpression regularExpressionWithPattern: #"(\\d+\\.\\d+)|(\\d+)|([+-/*///^])|([/(/)])" options:0 error:nil];
NSArray *matches = [ reg matchesInString: exp
options: 0
range:NSMakeRange(0, [exp length])];
return [matches mutableCopy];
}
int main (int argc, char *argv[]) {
NSString * exp = #"(2+5)";
NSMutableArray * array = [ExpressionTexting convertToList:exp];
NSLog(#"%#", array);
}
I get this output:
(
"<NSExtendedRegularExpressionCheckingResult: 0x10010d2e0>{0, 1}{<NSRegularExpression: 0x10010abb0> (\\d+\\.\\d+)|(\\d+)|([+-/*///^])|([/(/)]) 0x0}",
"<NSExtendedRegularExpressionCheckingResult: 0x10010d3f0>{1, 1}{<NSRegularExpression: 0x10010abb0> (\\d+\\.\\d+)|(\\d+)|([+-/*///^])|([/(/)]) 0x0}",
"<NSExtendedRegularExpressionCheckingResult: 0x10010d470>{2, 1}{<NSRegularExpression: 0x10010abb0> (\\d+\\.\\d+)|(\\d+)|([+-/*///^])|([/(/)]) 0x0}",
"<NSExtendedRegularExpressionCheckingResult: 0x10010d4f0>{3, 1}{<NSRegularExpression: 0x10010abb0> (\\d+\\.\\d+)|(\\d+)|([+-/*///^])|([/(/)]) 0x0}",
"<NSExtendedRegularExpressionCheckingResult: 0x10010d570>{4, 1}{<NSRegularExpression: 0x10010abb0> (\\d+\\.\\d+)|(\\d+)|([+-/*///^])|([/(/)]) 0x0}"
)
In Java I got the answer:
public static ArrayList<String> convertToList(String exp){
String regex = "(\\d+\\.\\d+)|(\\d+)|([+-/*///^])|([/(/)])";
Matcher m3 = Pattern.compile(regex).matcher(exp);
ArrayList<String> list = new ArrayList<String>(exp.length());
while (m3.find()) {
list.add(m3.group());
}
return list;
}
Which works, but I can't translate it in Objective-c
You should use regex pattern
(\\d+\\.?\\d*|\\.\\d+|[^\\d.])
Java code:
String str = "(2.5+1)*5";
Pattern p = Pattern.compile("(\\d+\\.?\\d*|\\.\\d+|[^\\d.])");
Matcher m = p.matcher(str);
while (m.find()) {
String match = m.group();
System.out.println(match);
}
Output:
(
2.5
+
1
)
*
5
Check this code here.
Maybe you need a math parser.
check DDMathParser
I found the solution, the error was due to the fact that I didn't really know how to handle the result, the NSArray returned is made of NSTextCheckingResult, which I have to use to extract the numbers and the operators.
+ (NSMutableArray*) convertToList: (NSString *) exp {
NSRegularExpression * reg = [NSRegularExpression regularExpressionWithPattern: #"(\\d+\\.\\d+)|(\\d+)|([+-/*///^])|([/(/)])" options:0 error:nil];
NSArray *matches = [ reg matchesInString: exp
options: 0
range:NSMakeRange(0, [exp length])];
NSMutableArray *mArray = [[NSMutableArray alloc] init];
for (NSTextCheckingResult *match in matches) {
NSString* matchText = [exp substringWithRange:[match range]];
[mArray addObject:matchText];
//NSLog(#"%#", matchText);
}
return mArray;
}
As was alluded to in another answer, DDMathParser can do this quite easily. It looks like you don't care about the hierarchy of the actual terms, so you could get away with just using the resulting tokens. That's easy:
NSError *error = nil;
DDMathStringTokenizer *tokenizer = [[DDMathStringTokenizer alloc] initWithString:#"(2.5+1)*5" error:&error];
NSArray *tokens = [tokenizer tokens];
tokens = [tokens valueForKey:#"token"];
NSLog(#"%#", tokens);
The -tokens method on DDMathStringTokenizer returns an array of DDMathStringToken objects. Those objects have an underlying NSString of the actual token (accessed via the token property), so you'll need the valueForKey: call on the array to turn it into an array of strings. But if you do this, then you'll get this logged:
(
"(",
"2.5",
"+",
1,
")",
"*",
5
)
I'm new of the Objective-C's code programming.... and is the first time that I post a question over Stackoverflow.
Somebody can say me why this piece of code don't works?
I've tested the Regular Expression on the test web site and work fine, but in Xcode don't works.... I need to validate a time in this format (HH:MM).
Thanks all...
(the code)
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *expression = [[NSString alloc] init];
if (textField == self.TB_orarioPartenza)
{
expression = #"^([0-1][0-9]|[2][0-3]):([0-5][0-9])$";
}
if (textField == self.TB_numGiri) {
expression = #"^d{2}";
}
NSString *newString =[textField.text stringByReplacingCharactersInRange:range withString:string];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:expression
options:0
error:nil];
NSUInteger numberOfMatches = [regex numberOfMatchesInString:newString
options:0
range:NSMakeRange(0, [newString length])];
if (numberOfMatches == 0) return NO;
return YES;
}
For h:mm format (for example "9:35") use use regex:
^(?:0?[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$
For hh:mm format (for example "09:35") use use regex:
^(?:0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$
I've tried this and it's not working:
range1 = NSMakeRange(0,[string length]);
NSRegularExpression *regex;
regex = [NSRegularExpression
regularExpressionWithPattern:#"([0-9]{3}) [0-9]{3}-[0-9]{4}"
options:0 error:NULL];
range2 = [regex rangeOfFirstMatchInString:string options:0 range:range1];
if (NSEqualRanges(range1, range2)) {
return YES;
}
// range2 always equals the "not found" range.
// Thx
I think it should be '\\' (double slashes) and not '\' (single slash)..
- (BOOL) validatePhone: (NSString *) candidate {
NSString *phoneRegex = #"\\(?([0-9]{3})\\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})";
NSPredicate *phoneTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", phoneRegex];
return [phoneTest evaluateWithObject:candidate];
}