4

I am trying to search for all words with brackets around them in a group of text and change it to italic in iOS. I am using this code to search for brackets in the text:

static inline NSRegularExpression * ParenthesisRegularExpression() {
    static NSRegularExpression *_parenthesisRegularExpression = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _parenthesisRegularExpression = [[NSRegularExpression alloc] initWithPattern:@"\\[[^\\(\\)]+\\]" options:NSRegularExpressionCaseInsensitive error:nil];
    });

    return _parenthesisRegularExpression;
}

I am using this to show me the matches:

NSRange matchRange = [result rangeAtIndex:0];
NSString *matchString = [[self.markerPointInfoDictionary objectForKey:@"long_description"] substringWithRange:matchRange];
NSLog(@"%@", matchString);

But it is returning me all the text from the first [ to the last ] in the group of text. There is a lot brackets in between.

I am using this code to change the text to italic:

-(TTTAttributedLabel*)setItalicTextForLabel:(TTTAttributedLabel*)attributedLabel fontSize:(float)Size
{
    [attributedLabel setText:[self.markerPointInfoDictionary objectForKey:@"long_description"] afterInheritingLabelAttributesAndConfiguringWithBlock:^NSMutableAttributedString *(NSMutableAttributedString *mutableAttributedString)
     {
         NSRange stringRange = NSMakeRange(0, [mutableAttributedString length]);
         NSRegularExpression *regexp = ParenthesisRegularExpression();
         UIFont *italicSystemFont = [UIFont italicSystemFontOfSize:Size];
         CTFontRef italicFont = CTFontCreateWithName((__bridge CFStringRef)italicSystemFont.fontName, italicSystemFont.pointSize, NULL);
         [regexp enumerateMatchesInString:[mutableAttributedString string] options:0 range:stringRange usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
             NSRange matchRange = [result rangeAtIndex:0];
             NSString *matchString = [[self.markerPointInfoDictionary objectForKey:@"long_description"] substringWithRange:matchRange];
             NSLog(@"%@", matchString);
             if (italicFont) {
                 [mutableAttributedString removeAttribute:(NSString *)kCTFontAttributeName range:result.range];
                 [mutableAttributedString addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id)italicFont range:result.range];
                 CFRelease(italicFont);
                 NSRange range1 = NSMakeRange (result.range.location, 1);
                 NSRange range2 = NSMakeRange (result.range.location + result.range.length -2 , 1);
                 [mutableAttributedString replaceCharactersInRange:range1 withString:@""];
                 [mutableAttributedString replaceCharactersInRange:range2 withString:@""];
             }
         }];
         return mutableAttributedString;
     }];

    return attributedLabel;
}

Btw my Text looks like this:

[hello] welcome [world], my [name] is [lakesh]

The results looks like this:

match string is [hello]
match string is [world]
match string is  is [lak

then crash..

Need some guidance to show me my mistake.

Alan Moore
  • 73,866
  • 12
  • 100
  • 156
lakshmen
  • 28,346
  • 66
  • 178
  • 276

2 Answers2

7

I haven't thought about the code you are showing, but your regular expression seems to be too greedy:

@"\\[[^\\(\\)]+\\]"

This matches 1-n characters between brackets "[]". The characters are defined by a character class that says "all characters except paranthesis", i.e. except "()".

In other words, your character class also matches bracket characters. The result is that your expression matches everything in between the first "[" and the last "]".

I suggest you try the following regular expression instead:

@"\\[[^\\[\\]]+\\]"

If you don't have nested brackets, you could even simplify it to this:

@"\\[[^\\]]+\\]"


EDIT

This is a simplified version of your code and the sample input text you provided, but using the regex that I have suggested. In my environment this works perfectly, it prints 4 lines to my debug output window. I don't know what causes your crash, so I suggest you start to simplify your own code step by step until you have found the problem.

NSString* mutableAttributedString = @"[hello] welcome [world], my [name] is [lakesh]";
NSRange stringRange = NSMakeRange(0, [mutableAttributedString length]);
NSRegularExpression* regexp = [[NSRegularExpression alloc] initWithPattern:@"\\[[^\\]]+\\]" options:NSRegularExpressionCaseInsensitive error:nil];
[regexp enumerateMatchesInString:mutableAttributedString
                         options:0
                           range:stringRange
                      usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop)
 {
   NSRange matchRange = [result rangeAtIndex:0];
   NSString* matchString = [mutableAttributedString substringWithRange:matchRange];
   NSLog(@"%@", matchString);
 }
 ];

The main differences to your code are:

  • I don't work with TTTAttributedLabel
  • I don't use NSMutableAttributedString
  • I don't use self.markerPointInfoDictionary
  • I don't have the if (italicFont) code block

So your problem should be in one of these areas.

herzbube
  • 13,158
  • 9
  • 45
  • 87
  • +1; I dropped this regex into my test app (I was trying to solve this as well) and you've got it first. Good job! Regex expressions can be incredibly tricky. My original answer idea was to use a NSScanner. – Michael Dautermann May 30 '13 at 10:17
  • @MichaelDautermann Thanks. `NSPredicate` can also be useful, but with those the number of backslashes needs to be doubled, making the regex even more obfuscated. – herzbube May 30 '13 at 10:42
  • it catches the first brackets correctly, but is wrong after that... misses by one... edited the question. – lakshmen May 30 '13 at 10:46
  • Without example text it is not possible to know if it is correct enough. Especially to handle overlaps and dangling open or close brackets – uchuugaka May 30 '13 at 11:18
  • Firstly thanks for replying and the searches. The problem i faced is that I am doing search, italicize and replace at the same time.. Do you have any solution to ensure that these three take place correctly... – lakshmen May 30 '13 at 15:24
  • @lakesh No, I do not have a solution. To be frank, I believe you should be able to find the solution yourself, after all you have already stated the root of your problem, namely that your code tries to do too much at the sime time. I can only give the general advice to do the obvious: _Simplify your code_! Make it sequential, make it ugly, whatever, until it does what you expect it to do. Once you have a working solution, start to make the code beautiful again. Do all this step by step and you should get there. Good luck. – herzbube May 30 '13 at 20:35
1

What's your crash log? Are you modifying the string in place with the match ranges from the original unmodified string? If so, you will run out of bounds before finishing.

You can consider a few othe approaches. You need to do the removal one at a time and stop trying to make your code do italics and character replacement in the same place. That is confusing you.

Your italics part is fine. But you need to do your character removal with an additional bit of iteration logic that gets a new match range from the niw shorter string after each character is removed.

uchuugaka
  • 12,679
  • 6
  • 37
  • 55
  • How to split the italics and character replacement? Any idea? what is the additional bit of iteration logic that is needed? – lakshmen May 30 '13 at 15:25