├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── RegExCategories.h ├── RegExCategories.m ├── TestProject ├── Objective-C-Regex-Categories.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Objective-C-Regex-Categories.xcscheme ├── Objective-C-Regex-Categories │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── Images │ │ └── icon.png │ ├── Objective-C-Regex-Categories-Info.plist │ ├── Objective-C-Regex-Categories-Prefix.pch │ ├── en.lproj │ │ └── InfoPlist.strings │ └── main.m └── Objective-C-Regex-CategoriesTests │ ├── Macros.m │ ├── NSRegularExpression+IndexOf.m │ ├── NSRegularExpression+Initialization.m │ ├── NSRegularExpression+IsMatch.m │ ├── NSRegularExpression+Matches.m │ ├── NSRegularExpression+Replace.m │ ├── NSRegularExpression+Split.m │ ├── NSString+IndexOf.m │ ├── NSString+Initialization.m │ ├── NSString+IsMatch.m │ ├── NSString+Matches.m │ ├── NSString+Replace.m │ ├── NSString+Split.m │ ├── Objective-C-Regex-CategoriesTests-Info.plist │ └── en.lproj │ └── InfoPlist.strings └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # CocoaPods 23 | Pods -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | script: 4 | - xctool -project TestProject/Objective-C-Regex-Categories.xcodeproj -scheme Objective-C-Regex-Categories -sdk iphonesimulator clean build test -freshSimulator -freshInstall -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2013 Josh Wright 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /RegExCategories.h: -------------------------------------------------------------------------------- 1 | // 2 | // RegExCategories.h 3 | // 4 | // https://github.com/bendytree/Objective-C-RegEx-Categories 5 | // 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2013 Josh Wright <@BendyTree> 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | #import 31 | 32 | 33 | /********************************************************/ 34 | /*********************** MACROS *************************/ 35 | /********************************************************/ 36 | 37 | /* 38 | * By default, we create an alias for NSRegularExpression 39 | * called `Rx` and creates a macro `RX()` for quick regex creation. 40 | * 41 | * If you don't want these macros, add the following statement 42 | * before you include this library: 43 | * 44 | * #define DisableRegExCategoriesMacros 45 | */ 46 | 47 | 48 | /** 49 | * Creates a macro (alias) for NSRegularExpression named `Rx`. 50 | * 51 | * ie. 52 | * NSRegularExpression* rx = [[Rx alloc] initWithPattern:@"\d+" options:0 error:nil]; 53 | */ 54 | 55 | #ifndef DisableRegExCategoriesMacros 56 | #define Rx NSRegularExpression 57 | #endif 58 | 59 | 60 | /** 61 | * Creates a macro (alias) for NSRegularExpression named `Rx`. 62 | * 63 | * ie. 64 | * NSRegularExpression* rx = [[Rx alloc] initWithPattern:@"\d+" options:0 error:nil]; 65 | */ 66 | 67 | #ifndef DisableRegExCategoriesMacros 68 | #define RX(pattern) [[NSRegularExpression alloc] initWithPattern:pattern] 69 | #endif 70 | 71 | 72 | 73 | /********************************************************/ 74 | /******************* MATCH OBJECTS **********************/ 75 | /********************************************************/ 76 | 77 | /** 78 | * RxMatch represents a single match. It contains the 79 | * matched value, range, sub groups, and the original 80 | * string. 81 | */ 82 | 83 | @interface RxMatch : NSObject 84 | @property (nonatomic, copy) NSString* value; /* The substring that matched the expression. */ 85 | @property (nonatomic, assign) NSRange range; /* The range of the original string that was matched. */ 86 | @property (nonatomic, copy) NSArray* groups; /* Each object is an RxMatchGroup. */ 87 | @property (nonatomic, copy) NSString* original; /* The full original string that was matched against. */ 88 | @end 89 | 90 | 91 | @interface RxMatchGroup : NSObject 92 | @property (nonatomic, copy) NSString* value; 93 | @property (nonatomic, assign) NSRange range; 94 | @end 95 | 96 | 97 | 98 | 99 | 100 | /** 101 | * Extend NSRegularExpression. 102 | */ 103 | 104 | @interface NSRegularExpression (ObjectiveCRegexCategories) 105 | 106 | 107 | /*******************************************************/ 108 | /******************* INITIALIZATION ********************/ 109 | /*******************************************************/ 110 | 111 | /** 112 | * Initialize an Rx object from a string. 113 | * 114 | * ie. 115 | * Rx* rx = [[Rx alloc] initWithString:@"\d+"]; 116 | * 117 | * Swift: 118 | * var rx = NSRegularExpression(pattern:"\d+"); 119 | */ 120 | 121 | - (NSRegularExpression*) initWithPattern:(NSString*)pattern; 122 | 123 | 124 | /** 125 | * Initialize an Rx object from a string. 126 | * 127 | * ie. 128 | * Rx* rx = [Rx rx:@"\d+"]; 129 | * 130 | * Swift: 131 | * var rx = NSRegularExpression.rx("\d+"); 132 | */ 133 | 134 | + (NSRegularExpression*) rx:(NSString*)pattern; 135 | 136 | 137 | /** 138 | * Initialize an Rx object from a string. By default, NSRegularExpression 139 | * is case sensitive, but this signature allows you to change that. 140 | * 141 | * ie. 142 | * Rx* rx = [Rx rx:@"\d+" ignoreCase:YES]; 143 | * 144 | * Swift: 145 | * var rx = NSRegularExpression.rx("\d+", ignoreCase: true); 146 | */ 147 | 148 | + (NSRegularExpression*) rx:(NSString*)pattern ignoreCase:(BOOL)ignoreCase; 149 | 150 | 151 | /** 152 | * Initialize an Rx object from a string and options. 153 | * 154 | * ie. 155 | * Rx* rx = [Rx rx:@"\d+" options:NSRegularExpressionCaseInsensitive]; 156 | * 157 | * Swift: 158 | * var rx = NSRegularExpression.rx("\d+", options: .CaseInsensitive); 159 | */ 160 | 161 | + (NSRegularExpression*) rx:(NSString*)pattern options:(NSRegularExpressionOptions)options; 162 | 163 | 164 | /*******************************************************/ 165 | /********************** IS MATCH ***********************/ 166 | /*******************************************************/ 167 | 168 | /** 169 | * Returns true if the string matches the regex. May also 170 | * be called on NSString as [@"\d" isMatch:rx]. 171 | * 172 | * ie. 173 | * Rx* rx = RX(@"\d+"); 174 | * BOOL isMatch = [rx isMatch:@"Dog #1"]; // => true 175 | * 176 | * Swift: 177 | * var rx = NSRegularExpression.rx("\d+"); 178 | * var isMatch = rx.isMatch("Dog #1"); // => true 179 | */ 180 | 181 | - (BOOL) isMatch:(NSString*)matchee; 182 | 183 | 184 | /** 185 | * Returns the index of the first match of the passed string. 186 | * 187 | * ie. 188 | * int i = [RX(@"\d+") indexOf:@"Buy 1 dog or buy 2?"]; // => 4 189 | */ 190 | 191 | - (int) indexOf:(NSString*)str; 192 | 193 | 194 | /** 195 | * Splits a string using the regex to identify delimeters. Returns 196 | * an NSArray of NSStrings. 197 | * 198 | * ie. 199 | * NSArray* pieces = [RX(@"[ ,]") split:@"A dog,cat"]; 200 | * => @[@"A", @"dog", @"cat"] 201 | */ 202 | 203 | - (NSArray*) split:(NSString*)str; 204 | 205 | 206 | /** 207 | * Replaces all occurrences in a string with a replacement string. 208 | * 209 | * ie. 210 | * NSString* result = [RX(@"ruf+") replace:@"ruf ruff!" with:@"meow"]; 211 | * => @"meow meow!" 212 | */ 213 | 214 | - (NSString*) replace:(NSString*)string with:(NSString*)replacement; 215 | 216 | 217 | /** 218 | * Replaces all occurrences of a regex using a block. The block receives the match 219 | * and should return the replacement. 220 | * 221 | * ie. 222 | * NSString* result = [RX(@"[A-Z]+") replace:@"i love COW" withBlock:^(NSString*){ return @"lamp"; }]; 223 | * => @"i love lamp" 224 | */ 225 | 226 | - (NSString*) replace:(NSString*)string withBlock:(NSString*(^)(NSString* match))replacer; 227 | 228 | 229 | /** 230 | * Replaces all occurrences of a regex using a block. The block receives a RxMatch object 231 | * that contains all the details of the match and should return a string 232 | * which is what the match is replaced with. 233 | * 234 | * ie. 235 | * NSString* result = [RX(@"\\w+") replace:@"hi bud" withDetailsBlock:^(RxMatch* match){ return [NSString stringWithFormat:@"%i", match.value.length]; }]; 236 | * => @"2 3" 237 | */ 238 | 239 | - (NSString*) replace:(NSString *)string withDetailsBlock:(NSString*(^)(RxMatch* match))replacer; 240 | 241 | 242 | /** 243 | * Returns an array of matched root strings with no other match information. 244 | * 245 | * ie. 246 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 247 | * NSArray* matches = [RX(@"\\w+[@]\\w+[.](\\w+)") matches:str]; 248 | * => @[ @"me@example.com", @"you@example.com" ] 249 | */ 250 | 251 | - (NSArray*) matches:(NSString*)str; 252 | 253 | 254 | /** 255 | * Returns a string which is the first match of the NSRegularExpression. 256 | * 257 | * ie. 258 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 259 | * NSString* match = [RX(@"\\w+[@]\\w+[.](\\w+)") firstMatch:str]; 260 | * => @"me@example.com" 261 | */ 262 | 263 | - (NSString*) firstMatch:(NSString*)str; 264 | 265 | 266 | /** 267 | * Returns an NSArray of RxMatch* objects. Each match contains the matched 268 | * value, range, groups, etc. 269 | * 270 | * ie. 271 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 272 | * NSArray* matches = [str matchesWithDetails:RX(@"\\w+[@]\\w+[.](\\w+)")]; 273 | */ 274 | 275 | - (NSArray*) matchesWithDetails:(NSString*)str; 276 | 277 | 278 | /** 279 | * Returns the first match as an RxMatch* object. 280 | * 281 | * ie. 282 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 283 | * Rx* rx = RX(@"\\w+[@]\\w+[.](\\w+)"); 284 | * RxMatch* match = [rx firstMatchWithDetails:str]; 285 | */ 286 | 287 | - (RxMatch*) firstMatchWithDetails:(NSString*)str; 288 | 289 | @end 290 | 291 | 292 | 293 | /** 294 | * A category on NSString to make it easy to use 295 | * Rx in simple operations. 296 | */ 297 | 298 | @interface NSString (ObjectiveCRegexCategories) 299 | 300 | 301 | /** 302 | * Initialize an NSRegularExpression object from a string. 303 | * 304 | * ie. 305 | * NSRegularExpression* rx = [@"\d+" toRx]; 306 | */ 307 | 308 | - (NSRegularExpression*) toRx; 309 | 310 | 311 | /** 312 | * Initialize an NSRegularExpression object from a string with 313 | * a flag denoting case-sensitivity. By default, NSRegularExpression 314 | * is case sensitive. 315 | * 316 | * ie. 317 | * NSRegularExpression* rx = [@"\d+" toRxIgnoreCase:YES]; 318 | */ 319 | 320 | - (NSRegularExpression*) toRxIgnoreCase:(BOOL)ignoreCase; 321 | 322 | 323 | /** 324 | * Initialize an NSRegularExpression object from a string with options. 325 | * 326 | * ie. 327 | * NSRegularExpression* rx = [@"\d+" toRxWithOptions:NSRegularExpressionCaseInsensitive]; 328 | */ 329 | 330 | - (NSRegularExpression*) toRxWithOptions:(NSRegularExpressionOptions)options; 331 | 332 | 333 | /** 334 | * Returns true if the string matches the regex. May also 335 | * be called as on Rx as [rx isMatch:@"some string"]. 336 | * 337 | * ie. 338 | * BOOL isMatch = [@"Dog #1" isMatch:RX(@"\d+")]; // => true 339 | */ 340 | 341 | - (BOOL) isMatch:(NSRegularExpression*)rx; 342 | 343 | 344 | /** 345 | * Returns the index of the first match according to 346 | * the regex passed in. 347 | * 348 | * ie. 349 | * int i = [@"Buy 1 dog or buy 2?" indexOf:RX(@"\d+")]; // => 4 350 | */ 351 | 352 | - (int) indexOf:(NSRegularExpression*)rx; 353 | 354 | 355 | /** 356 | * Splits a string using the regex to identify delimeters. Returns 357 | * an NSArray of NSStrings. 358 | * 359 | * ie. 360 | * NSArray* pieces = [@"A dog,cat" split:RX(@"[ ,]")]; 361 | * => @[@"A", @"dog", @"cat"] 362 | */ 363 | 364 | - (NSArray*) split:(NSRegularExpression*)rx; 365 | 366 | 367 | /** 368 | * Replaces all occurrences of a regex with a replacement string. 369 | * 370 | * ie. 371 | * NSString* result = [@"ruf ruff!" replace:RX(@"ruf+") with:@"meow"]; 372 | * => @"meow meow!" 373 | */ 374 | 375 | - (NSString*) replace:(NSRegularExpression*)rx with:(NSString*)replacement; 376 | 377 | 378 | /** 379 | * Replaces all occurrences of a regex using a block. The block receives the match 380 | * and should return the replacement. 381 | * 382 | * ie. 383 | * NSString* result = [@"i love COW" replace:RX(@"[A-Z]+") withBlock:^(NSString*){ return @"lamp"; }]; 384 | * => @"i love lamp" 385 | */ 386 | 387 | - (NSString*) replace:(NSRegularExpression *)rx withBlock:(NSString*(^)(NSString* match))replacer; 388 | 389 | 390 | /** 391 | * Replaces all occurrences of a regex using a block. The block receives an RxMatch 392 | * object which contains all of the details for each match and should return a string 393 | * which is what the match is replaced with. 394 | * 395 | * ie. 396 | * NSString* result = [@"hi bud" replace:RX(@"\\w+") withDetailsBlock:^(RxMatch* match){ return [NSString stringWithFormat:@"%i", match.value.length]; }]; 397 | * => @"2 3" 398 | */ 399 | 400 | - (NSString*) replace:(NSRegularExpression *)rx withDetailsBlock:(NSString*(^)(RxMatch* match))replacer; 401 | 402 | 403 | /** 404 | * Returns an array of matched root strings with no other match information. 405 | * 406 | * ie. 407 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 408 | * NSArray* matches = [str matches:RX(@"\\w+[@]\\w+[.](\\w+)")]; 409 | * => @[ @"me@example.com", @"you@example.com" ] 410 | */ 411 | 412 | - (NSArray*) matches:(NSRegularExpression*)rx; 413 | 414 | 415 | /** 416 | * Returns a string which is the first match of the NSRegularExpression. 417 | * 418 | * ie. 419 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 420 | * NSString* match = [str firstMatch:RX(@"\\w+[@]\\w+[.](\\w+)")]; 421 | * => @"me@example.com" 422 | */ 423 | 424 | - (NSString*) firstMatch:(NSRegularExpression*)rx; 425 | 426 | 427 | /** 428 | * Returns an NSArray of RxMatch* objects. Each match contains the matched 429 | * value, range, groups, etc. 430 | * 431 | * ie. 432 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 433 | * NSArray* matches = [str matchesWithDetails:RX(@"\\w+[@]\\w+[.](\\w+)")]; 434 | */ 435 | 436 | - (NSArray*) matchesWithDetails:(NSRegularExpression*)rx; 437 | 438 | 439 | /** 440 | * Returns an the first match as an RxMatch* object. 441 | * 442 | * ie. 443 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 444 | * RxMatch* match = [str firstMatchWithDetails:RX(@"\\w+[@]\\w+[.](\\w+)")]; 445 | */ 446 | 447 | - (RxMatch*) firstMatchWithDetails:(NSRegularExpression*)rx; 448 | 449 | @end 450 | 451 | -------------------------------------------------------------------------------- /RegExCategories.m: -------------------------------------------------------------------------------- 1 | // 2 | // RegExCategories.m 3 | // 4 | // https://github.com/bendytree/Objective-C-RegEx-Categories 5 | // 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2013 Josh Wright <@BendyTree> 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | #import "RegExCategories.h" 31 | 32 | @implementation NSRegularExpression (ObjectiveCRegexCategories) 33 | 34 | - (id) initWithPattern:(NSString*)pattern 35 | { 36 | return [self initWithPattern:pattern options:0 error:nil]; 37 | } 38 | 39 | + (NSRegularExpression*) rx:(NSString*)pattern 40 | { 41 | return [[self alloc] initWithPattern:pattern]; 42 | } 43 | 44 | + (NSRegularExpression*) rx:(NSString*)pattern ignoreCase:(BOOL)ignoreCase 45 | { 46 | return [[self alloc] initWithPattern:pattern options:ignoreCase?NSRegularExpressionCaseInsensitive:0 error:nil]; 47 | } 48 | 49 | + (NSRegularExpression*) rx:(NSString*)pattern options:(NSRegularExpressionOptions)options 50 | { 51 | return [[self alloc] initWithPattern:pattern options:options error:nil]; 52 | } 53 | 54 | - (BOOL) isMatch:(NSString*)matchee 55 | { 56 | return [self numberOfMatchesInString:matchee options:0 range:NSMakeRange(0, matchee.length)] > 0; 57 | } 58 | 59 | - (int) indexOf:(NSString*)matchee 60 | { 61 | NSRange range = [self rangeOfFirstMatchInString:matchee options:0 range:NSMakeRange(0, matchee.length)]; 62 | return range.location == NSNotFound ? -1 : (int)range.location; 63 | } 64 | 65 | - (NSArray*) split:(NSString *)str 66 | { 67 | NSRange range = NSMakeRange(0, str.length); 68 | 69 | //get locations of matches 70 | NSMutableArray* matchingRanges = [NSMutableArray array]; 71 | NSArray* matches = [self matchesInString:str options:0 range:range]; 72 | for(NSTextCheckingResult* match in matches) { 73 | [matchingRanges addObject:[NSValue valueWithRange:match.range]]; 74 | } 75 | 76 | //invert ranges - get ranges of non-matched pieces 77 | NSMutableArray* pieceRanges = [NSMutableArray array]; 78 | 79 | //add first range 80 | [pieceRanges addObject:[NSValue valueWithRange:NSMakeRange(0, 81 | (matchingRanges.count == 0 ? str.length : [matchingRanges[0] rangeValue].location))]]; 82 | 83 | //add between splits ranges and last range 84 | for(int i=0; i=0; i--) { 120 | NSTextCheckingResult* match = matches[i]; 121 | NSString* matchStr = [string substringWithRange:match.range]; 122 | NSString* replacement = replacer(matchStr); 123 | [result replaceCharactersInRange:match.range withString:replacement]; 124 | } 125 | 126 | return result; 127 | } 128 | 129 | - (NSString*) replace:(NSString *)string withDetailsBlock:(NSString*(^)(RxMatch* match))replacer 130 | { 131 | //no replacer? just return 132 | if (!replacer) return string; 133 | 134 | //copy the string so we can replace subsections 135 | NSMutableString* replaced = [string mutableCopy]; 136 | 137 | //get matches 138 | NSArray* matches = [self matchesInString:string options:0 range:NSMakeRange(0, string.length)]; 139 | 140 | //replace each match (right to left so indexing doesn't get messed up) 141 | for (int i=(int)matches.count-1; i>=0; i--) { 142 | NSTextCheckingResult* result = matches[i]; 143 | RxMatch* match = [self resultToMatch:result original:string]; 144 | NSString* replacement = replacer(match); 145 | [replaced replaceCharactersInRange:result.range withString:replacement]; 146 | } 147 | 148 | return replaced; 149 | } 150 | 151 | - (NSArray*) matches:(NSString*)str 152 | { 153 | NSMutableArray* matches = [NSMutableArray array]; 154 | 155 | NSArray* results = [self matchesInString:str options:0 range:NSMakeRange(0, str.length)]; 156 | for (NSTextCheckingResult* result in results) { 157 | NSString* match = [str substringWithRange:result.range]; 158 | [matches addObject:match]; 159 | } 160 | 161 | return matches; 162 | } 163 | 164 | - (NSString*) firstMatch:(NSString*)str 165 | { 166 | NSTextCheckingResult* match = [self firstMatchInString:str options:0 range:NSMakeRange(0, str.length)]; 167 | 168 | if (!match) return nil; 169 | 170 | return [str substringWithRange:match.range]; 171 | } 172 | 173 | - (RxMatch*) resultToMatch:(NSTextCheckingResult*)result original:(NSString*)original 174 | { 175 | RxMatch* match = [[RxMatch alloc] init]; 176 | match.original = original; 177 | match.range = result.range; 178 | match.value = result.range.length ? [original substringWithRange:result.range] : nil; 179 | 180 | //groups 181 | NSMutableArray* groups = [NSMutableArray array]; 182 | for(int i=0; i 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-Categories.xcodeproj/xcshareddata/xcschemes/Objective-C-Regex-Categories.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-Categories/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Objective-C-Regex-Categories 4 | // 5 | 6 | 7 | #import 8 | 9 | @interface AppDelegate : UIResponder 10 | 11 | @property (strong, nonatomic) UIWindow *window; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-Categories/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Objective-C-Regex-Categories 4 | // 5 | 6 | 7 | #import "AppDelegate.h" 8 | 9 | @implementation AppDelegate 10 | 11 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 12 | { 13 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 14 | // Override point for customization after application launch. 15 | self.window.backgroundColor = [UIColor whiteColor]; 16 | [self.window makeKeyAndVisible]; 17 | return YES; 18 | } 19 | 20 | - (void)applicationWillResignActive:(UIApplication *)application 21 | { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 24 | } 25 | 26 | - (void)applicationDidEnterBackground:(UIApplication *)application 27 | { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | - (void)applicationWillEnterForeground:(UIApplication *)application 33 | { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application 38 | { 39 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 40 | } 41 | 42 | - (void)applicationWillTerminate:(UIApplication *)application 43 | { 44 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-Categories/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "ipad", 20 | "size" : "29x29", 21 | "scale" : "1x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "29x29", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "40x40", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "40x40", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "76x76", 41 | "scale" : "1x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "76x76", 46 | "scale" : "2x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-Categories/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "orientation" : "portrait", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "full-screen", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "orientation" : "portrait", 34 | "idiom" : "ipad", 35 | "extent" : "full-screen", 36 | "minimum-system-version" : "7.0", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "landscape", 41 | "idiom" : "ipad", 42 | "extent" : "full-screen", 43 | "minimum-system-version" : "7.0", 44 | "scale" : "2x" 45 | } 46 | ], 47 | "info" : { 48 | "version" : 1, 49 | "author" : "xcode" 50 | } 51 | } -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-Categories/Images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendytree/Objective-C-RegEx-Categories/8abd7fa8de5a69b6c448db5595f89ac1c1b6ca7c/TestProject/Objective-C-Regex-Categories/Images/icon.png -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-Categories/Objective-C-Regex-Categories-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.bendytree.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-Categories/Objective-C-Regex-Categories-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_3_0 10 | #warning "This project uses features only available in iOS SDK 3.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-Categories/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-Categories/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Objective-C-Regex-Categories 4 | // 5 | 6 | 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-CategoriesTests/Macros.m: -------------------------------------------------------------------------------- 1 | // 2 | // Macros.m 3 | // Objective-C-Regex-Categories 4 | // 5 | 6 | 7 | #import 8 | #import "RegExCategories.h" 9 | 10 | @interface Macros : XCTestCase @end 11 | 12 | 13 | @implementation Macros 14 | 15 | - (void) test_RX_is_a_macro_for_creating_an_NSRegularExpression 16 | { 17 | BOOL isRx = [RX(@".") isMemberOfClass:[NSRegularExpression class]]; 18 | XCTAssert(isRx, @"Expected RX to create a NSRegularExpression."); 19 | } 20 | 21 | - (void) test_Rx_is_an_alias_for_NSRegularExpression 22 | { 23 | XCTAssertEqualObjects([Rx class], [NSRegularExpression class], @"Expected them to be the same class."); 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-CategoriesTests/NSRegularExpression+IndexOf.m: -------------------------------------------------------------------------------- 1 | // 2 | // IndexOf.m 3 | // Objective-C-Regex-Categories 4 | // 5 | 6 | 7 | #import 8 | #import "RegExCategories.h" 9 | 10 | @interface NSRegularExpression_IndexOf : XCTestCase @end 11 | 12 | 13 | @implementation NSRegularExpression_IndexOf 14 | 15 | - (void) test_indexOf_on_matching_string_returns_index_of_first_match 16 | { 17 | int i = [RX(@"\\d") indexOf:@"You 2 can have 3 cows."]; 18 | XCTAssertEqual(i, 4, @"Expected to match index 4."); 19 | } 20 | 21 | - (void) test_indexOf_on_non_matching_string_returns_negative_one 22 | { 23 | int i = [RX(@"\\d") indexOf:@"You two can have three cows."]; 24 | XCTAssertEqual(i, -1, @"Expected to match index -1."); 25 | } 26 | 27 | - (void) test_indexOf_on_matching_rx_returns_index_of_first_match 28 | { 29 | int i = [RX(@"\\d") indexOf:@"You 2 can have 3 cows."]; 30 | XCTAssertEqual(i, 4, @"Expected to match index 4."); 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-CategoriesTests/NSRegularExpression+Initialization.m: -------------------------------------------------------------------------------- 1 | // 2 | // Initialization.m 3 | // Objective-C-Regex-Categories 4 | // 5 | 6 | 7 | #import 8 | #import "RegExCategories.h" 9 | 10 | @interface NSRegularExpression_Initialization : XCTestCase @end 11 | 12 | 13 | @implementation NSRegularExpression_Initialization 14 | 15 | - (void) test_NSRegularExpression_can_be_created_using_class_method_rx 16 | { 17 | BOOL isRx = [[Rx rx:@"."] isMemberOfClass:[Rx class]]; 18 | XCTAssert(isRx, @"Expected [Rx rx:] to create an Rx object."); 19 | } 20 | 21 | - (void) test_NSRegularExpression_can_be_created_using_class_method_rx_with_casing 22 | { 23 | BOOL isRx = [[Rx rx:@"." ignoreCase:YES] isMemberOfClass:[Rx class]]; 24 | XCTAssert(isRx, @"Expected [Rx rx:ignoreCase:] to create an Rx object."); 25 | } 26 | 27 | - (void) test_NSRegularExpression_can_be_created_using_class_method_rx_with_options 28 | { 29 | BOOL isRx = [[Rx rx:@"." options:NSRegularExpressionCaseInsensitive] isMemberOfClass:[Rx class]]; 30 | XCTAssert(isRx, @"Expected [Rx rx:options:] to create an Rx object."); 31 | } 32 | 33 | - (void) test_NSRegularExpression_can_be_created_using_initWithPattern 34 | { 35 | BOOL isRx = [[[Rx alloc] initWithPattern:@"."] isMemberOfClass:[Rx class]]; 36 | XCTAssert(isRx, @"Expected [Rx rx:] to create an Rx object."); 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-CategoriesTests/NSRegularExpression+IsMatch.m: -------------------------------------------------------------------------------- 1 | // 2 | // IsMatch.m 3 | // Objective-C-Regex-Categories 4 | // 5 | 6 | 7 | #import 8 | #import "RegExCategories.h" 9 | 10 | @interface NSRegularExpression_IsMatch : XCTestCase @end 11 | 12 | 13 | @implementation NSRegularExpression_IsMatch 14 | 15 | - (void) test_is_match_returns_false_for_zero_matches 16 | { 17 | BOOL isMatch = [RX(@".at") isMatch:@"I am a dog."]; 18 | XCTAssert(!isMatch, @"Expected isMatch to return false."); 19 | } 20 | 21 | - (void) test_is_match_returns_true_for_one_match 22 | { 23 | BOOL isMatch = [RX(@".at") isMatch:@"I know a cat."]; 24 | XCTAssert(isMatch, @"Expected isMatch to return true."); 25 | } 26 | 27 | - (void) test_is_match_returns_true_for_multiple_matches 28 | { 29 | BOOL isMatch = [RX(@".at") isMatch:@"I eat cats."]; 30 | XCTAssert(isMatch, @"Expected isMatch to return true."); 31 | } 32 | 33 | - (void) test_is_match_defaults_to_being_case_sensitive 34 | { 35 | BOOL isMatch = [RX(@"dog") isMatch:@"Dogs are nice."]; 36 | XCTAssert(!isMatch, @"Expected isMatch to return false."); 37 | } 38 | 39 | - (void) test_is_match_can_be_case_insensitive_using_options 40 | { 41 | NSRegularExpression* rx = [[NSRegularExpression alloc] initWithPattern:@"dog" options:NSRegularExpressionCaseInsensitive error:nil]; 42 | BOOL isMatch = [rx isMatch:@"Dogs are nice."]; 43 | XCTAssert(isMatch, @"Expected isMatch to return true."); 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-CategoriesTests/NSRegularExpression+Matches.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSRegularExpression+Matches.m 3 | // Objective-C-Regex-Categories 4 | // 5 | 6 | 7 | #import 8 | #import "RegExCategories.h" 9 | 10 | 11 | @interface NSRegularExpression_Matches : XCTestCase @end 12 | 13 | 14 | @implementation NSRegularExpression_Matches 15 | 16 | - (void) test_Matches_returns_an_array_of_matched_strings_without_other_match_information 17 | { 18 | NSString* str = @"My email is me@example.com and yours is you@example.com"; 19 | NSArray* matches = [RX(@"\\w+[@]\\w+[.](\\w+)") matches:str]; 20 | XCTAssertEqual(matches.count, 2U, @"Should have 2 matches."); 21 | XCTAssertEqualObjects(matches[0], @"me@example.com", @"First match should be 'me@example.com'."); 22 | XCTAssertEqualObjects(matches[1], @"you@example.com", @"Second match should be 'you@example.com'."); 23 | } 24 | 25 | - (void) test_First_match_returns_the_first_match 26 | { 27 | NSString* str = @"My email is me@example.com and yours is you@example.com"; 28 | NSString* match = [RX(@"\\w+[@]\\w+[.](\\w+)") firstMatch:str]; 29 | XCTAssertEqualObjects(match, @"me@example.com", @"First match should be 'me@example.com'."); 30 | } 31 | 32 | - (void) test_First_match_returns_nil_for_no_matches 33 | { 34 | NSString* match = [RX(@"\\d") firstMatch:@"Cats and dogs."]; 35 | XCTAssertEqualObjects(match, nil, @"Match should be nil."); 36 | } 37 | 38 | - (void) test_matchesWithDetails_returns_array_of_matches_with_details 39 | { 40 | NSString* str = @"My email is me@example.com and yours is you@example.com"; 41 | NSArray* matches = [str matchesWithDetails:RX(@"\\w+[@]\\w+[.](\\w+)")]; 42 | 43 | //two matches should be found 44 | XCTAssertEqual(matches.count, 2U, @"Should have 2 matches."); 45 | 46 | //all matches should be RxMatch objects 47 | for (id match in matches){ 48 | XCTAssertEqualObjects([match class], [RxMatch class], @"Each item in the array should be a details object."); 49 | } 50 | 51 | //verify the first match 52 | RxMatch* match = matches[0]; 53 | XCTAssertEqualObjects(match.value, @"me@example.com", @"Value should be 'me@example.com'."); 54 | XCTAssertEqualObjects(match.original, @"My email is me@example.com and yours is you@example.com", @"Match contains the original string."); 55 | XCTAssertEqual(match.range.location, 12U, @"Location should be 12."); 56 | XCTAssertEqual(match.range.length, 14U, @"Length should be 14."); 57 | XCTAssert([match.groups isKindOfClass:[NSArray class]], @"Groups should be an NSArray."); 58 | XCTAssertEqual(match.groups.count, 2U, @"Groups should be an NSArray."); 59 | 60 | RxMatchGroup* groupZero = (RxMatchGroup*)match.groups[0]; 61 | XCTAssert([groupZero isKindOfClass:[RxMatchGroup class]], @"Each group is an RxMatchGroup."); 62 | XCTAssertEqualObjects(groupZero.value, @"me@example.com", @"First group is always the complete match."); 63 | XCTAssertEqual(groupZero.range.location, 12U, @"First group's range is same as match's range."); 64 | XCTAssertEqual(groupZero.range.length, 14U, @"First group's range is same as match's range."); 65 | 66 | RxMatchGroup* groupOne = (RxMatchGroup*)match.groups[1]; 67 | XCTAssertEqualObjects(groupOne.value, @"com", @"Second group is the first captured group."); 68 | XCTAssertEqual(groupOne.range.location, 23U, @"Location should be 23."); 69 | XCTAssertEqual(groupOne.range.length, 3U, @"Length should be 3."); 70 | } 71 | 72 | - (void) test_firstMatchWithDetails_returns_an_RxMatch_object 73 | { 74 | NSString* str = @"My email is me@example.com and yours is you@example.com"; 75 | RxMatch* match = [str firstMatchWithDetails:RX(@"\\w+[@]\\w+[.](\\w+)")]; 76 | 77 | XCTAssertEqualObjects(match.value, @"me@example.com", @"First match should be 'me@example.com'."); 78 | } 79 | 80 | - (void) test_firstMatchWithDetails_returns_nil_for_no_matches 81 | { 82 | RxMatch* match = [@"Cats and dogs." firstMatchWithDetails:RX(@"\\d")]; 83 | 84 | XCTAssert(match == nil, @"Match should be nil."); 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-CategoriesTests/NSRegularExpression+Replace.m: -------------------------------------------------------------------------------- 1 | // 2 | // Replace.m 3 | // Objective-C-Regex-Categories 4 | // 5 | 6 | 7 | #import 8 | #import "RegExCategories.h" 9 | 10 | @interface NSRegularExpression_Replace : XCTestCase @end 11 | 12 | 13 | @implementation NSRegularExpression_Replace 14 | 15 | - (void) test_replace_replaces_with_back_references 16 | { 17 | NSString* result = [RX(@"(\\d{3}).?(\\d{3}).?(\\d{4})") replace:@"5551234567" with:@"1 ($1) $2-$3"]; 18 | XCTAssertEqualObjects(result, @"1 (555) 123-4567", @"Expected a properly formatted phone number."); 19 | } 20 | 21 | - (void) test_replace_can_be_called_from_an_NSRegularExpression 22 | { 23 | NSString* result = [RX(@"ruf+") replace:@"ruff ruf!" with:@"meow"]; 24 | XCTAssertEqualObjects(result, @"meow meow!", @"Expected to replace both barks."); 25 | } 26 | 27 | - (void) test_replace_replaces_with_callback 28 | { 29 | NSString* result = [RX(@"[A-Z]+") replace:@"i love COW" withBlock:^(NSString* match){ return @"lamp"; }]; 30 | XCTAssertEqualObjects(result, @"i love lamp", @"Result should be 'I love lamp'."); 31 | } 32 | 33 | - (void) test_replace_replaces_with_nil_callback_returns_original_string 34 | { 35 | NSString* result = [RX(@"[A-Z]+") replace:@"i love COW" withBlock:nil]; 36 | XCTAssertEqualObjects(result, @"i love COW", @"Result should be unchanged."); 37 | } 38 | 39 | - (void) test_replace_replaces_with_sets_callback 40 | { 41 | NSString* result = [RX(@"\\w+") replace:@"hi bud" withDetailsBlock:^(RxMatch* match){ return [NSString stringWithFormat:@"%lu", (unsigned long)match.value.length]; }]; 42 | XCTAssertEqualObjects(result, @"2 3", @"Result should be '2 3'."); 43 | } 44 | 45 | - (void) test_replace_replaces_with_nil_sets_callback_returns_original_string 46 | { 47 | NSString* result = [RX(@".") replace:@"hi bud" withDetailsBlock:nil]; 48 | XCTAssertEqualObjects(result, @"hi bud", @"Result should be the original string."); 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-CategoriesTests/NSRegularExpression+Split.m: -------------------------------------------------------------------------------- 1 | // 2 | // Split.m 3 | // Objective-C-Regex-Categories 4 | // 5 | 6 | 7 | #import 8 | #import "RegExCategories.h" 9 | 10 | @interface NSRegularExpression_Split : XCTestCase @end 11 | 12 | 13 | @implementation NSRegularExpression_Split 14 | 15 | - (void) test_split_string_on_regex_returns_array_of_strings 16 | { 17 | NSArray* pieces = [RX(@"[ ,]") split:@"I like cats,dogs"]; 18 | XCTAssertEqual(pieces.count, 4U, @"Expected array to contain 4 items."); 19 | XCTAssertEqualObjects(pieces[0], @"I", @"Expected first item to be 'I'."); 20 | XCTAssertEqualObjects(pieces[1], @"like", @"Expected second item to be 'I'."); 21 | XCTAssertEqualObjects(pieces[2], @"cats", @"Expected third item to be 'cats'."); 22 | XCTAssertEqualObjects(pieces[3], @"dogs", @"Expected fourth item to be 'dogs'."); 23 | } 24 | 25 | - (void) test_capture_groups_are_ignored_on_split_strings 26 | { 27 | NSArray* pieces = [RX(@"(( |,))") split:@"I like cats,dogs"]; 28 | XCTAssertEqual(pieces.count, 4U, @"Expected array to contain 4 items."); 29 | XCTAssertEqualObjects(pieces[0], @"I", @"Expected first item to be 'I'."); 30 | XCTAssertEqualObjects(pieces[1], @"like", @"Expected second item to be 'I'."); 31 | XCTAssertEqualObjects(pieces[2], @"cats", @"Expected third item to be 'cats'."); 32 | XCTAssertEqualObjects(pieces[3], @"dogs", @"Expected fourth item to be 'dogs'."); 33 | } 34 | 35 | - (void) test_split_string_with_no_matches_returns_array_with_original_string 36 | { 37 | NSArray* pieces = [RX(@",") split:@"Hey dog."]; 38 | XCTAssertEqual(pieces.count, 1U, @"Expected array to contain 1 item."); 39 | XCTAssertEqualObjects(pieces[0], @"Hey dog.", @"Expected first item to be 'Hey dog.'."); 40 | } 41 | 42 | - (void) test_split_string_keeps_empty_entries 43 | { 44 | NSArray* pieces = [RX(@"[.]") split:@".Hey..dog."]; 45 | XCTAssertEqual(pieces.count, 5U, @"Expected array to contain 5 items."); 46 | XCTAssertEqualObjects(pieces[0], @"", @"Expected first item to be ''."); 47 | XCTAssertEqualObjects(pieces[1], @"Hey", @"Expected second item to be 'Hey'."); 48 | XCTAssertEqualObjects(pieces[2], @"", @"Expected third item to be ''."); 49 | XCTAssertEqualObjects(pieces[3], @"dog", @"Expected fourth item to be 'dog'."); 50 | XCTAssertEqualObjects(pieces[4], @"", @"Expected fifth item to be ''."); 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-CategoriesTests/NSString+IndexOf.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+IndexOf.m 3 | // Objective-C-Regex-Categories 4 | // 5 | 6 | 7 | #import 8 | #import "RegExCategories.h" 9 | 10 | @interface NSString_IndexOf : XCTestCase @end 11 | 12 | 13 | @implementation NSString_IndexOf 14 | 15 | - (void) test_indexOf_can_be_called_on_an_NSString 16 | { 17 | int i = [@"You 2 can have 3 cows." indexOf:RX(@"\\d")]; 18 | XCTAssertEqual(i, 4, @"Expected to match index 4."); 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-CategoriesTests/NSString+Initialization.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+Initialization.m 3 | // Objective-C-Regex-Categories 4 | // 5 | 6 | 7 | #import 8 | #import "RegExCategories.h" 9 | 10 | @interface NSString_Initialization : XCTestCase @end 11 | 12 | 13 | @implementation NSString_Initialization 14 | 15 | - (void) test_NSRegularExpression_can_be_created_using_category_on_NSString 16 | { 17 | Rx* rx = [@"." toRx]; 18 | BOOL isRx = [rx isMemberOfClass:[Rx class]]; 19 | XCTAssert(isRx, @"Expected toRx on NSString to create an Rx object."); 20 | } 21 | 22 | - (void) test_NSRegularExpression_can_be_created_using_category_on_NSString_and_ignoreCase 23 | { 24 | Rx* rx = [@"." toRxIgnoreCase:YES]; 25 | BOOL isRx = [rx isMemberOfClass:[Rx class]]; 26 | XCTAssert(isRx, @"Expected toRxIgnoreCase: on NSString to create an Rx object."); 27 | } 28 | 29 | - (void) test_NSRegularExpression_can_be_created_using_category_on_NSString_with_options 30 | { 31 | Rx* rx = [@"." toRxWithOptions:NSRegularExpressionCaseInsensitive]; 32 | BOOL isRx = [rx isMemberOfClass:[Rx class]]; 33 | XCTAssert(isRx, @"Expected toRxIgnoreCase: on NSString to create an Rx object."); 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-CategoriesTests/NSString+IsMatch.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+IsMatch.m 3 | // Objective-C-Regex-Categories 4 | // 5 | 6 | 7 | #import 8 | #import "RegExCategories.h" 9 | 10 | @interface NSString_IsMatch : XCTestCase @end 11 | 12 | 13 | @implementation NSString_IsMatch 14 | 15 | - (void) test_isMatch_can_be_called_on_an_NSString 16 | { 17 | BOOL isMatch = [@"I am a dog." isMatch:RX(@".at")]; 18 | XCTAssert(!isMatch, @"Expected isMatch to return false."); 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-CategoriesTests/NSString+Matches.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+Matches.m 3 | // Objective-C-Regex-Categories 4 | // 5 | 6 | 7 | #import 8 | #import "RegExCategories.h" 9 | 10 | @interface NSString_Matches : XCTestCase @end 11 | 12 | 13 | @implementation NSString_Matches 14 | 15 | - (void) test_Matches_can_be_called_as_a_category_on_NSString 16 | { 17 | NSString* str = @"My email is me@example.com and yours is you@example.com"; 18 | NSArray* matches = [str matches:RX(@"\\w+[@]\\w+[.](\\w+)")]; 19 | XCTAssertEqual(matches.count, 2U, @"Should have 2 matches."); 20 | XCTAssertEqualObjects(matches[0], @"me@example.com", @"First match should be 'me@example.com'."); 21 | XCTAssertEqualObjects(matches[1], @"you@example.com", @"Second match should be 'you@example.com'."); 22 | } 23 | 24 | - (void) test_First_match_can_be_called_as_a_category_on_NSString 25 | { 26 | NSString* str = @"My email is me@example.com and yours is you@example.com"; 27 | NSString* match = [str firstMatch:RX(@"\\w+[@]\\w+[.](\\w+)")]; 28 | XCTAssertEqualObjects(match, @"me@example.com", @"First match should be 'me@example.com'."); 29 | } 30 | 31 | - (void) test_matchesWithDetails_can_be_called_as_a_category_on_NSString 32 | { 33 | NSString* str = @"My email is me@example.com and yours is you@example.com"; 34 | NSArray* matches = [str matchesWithDetails:RX(@"\\w+[@]\\w+[.](\\w+)")]; 35 | 36 | //two matches should be found 37 | XCTAssertEqual(matches.count, 2U, @"Should have 2 matches."); 38 | 39 | //all matches should be RxMatch objects 40 | for (id match in matches){ 41 | XCTAssertEqualObjects([match class], [RxMatch class], @"Each item in the array should be a details object."); 42 | } 43 | 44 | //full verification is done in NSRegularExpression+Matches.m 45 | } 46 | 47 | - (void) test_firstMatchWithDetails_can_be_called_as_a_category_on_NSString 48 | { 49 | NSString* str = @"My email is me@example.com and yours is you@example.com"; 50 | RxMatch* match = [str firstMatchWithDetails:RX(@"\\w+[@]\\w+[.](\\w+)")]; 51 | 52 | XCTAssertEqualObjects(match.value, @"me@example.com", @"First match should be 'me@example.com'."); 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-CategoriesTests/NSString+Replace.m: -------------------------------------------------------------------------------- 1 | // 2 | // Replace.m 3 | // Objective-C-Regex-Categories 4 | // 5 | 6 | 7 | #import 8 | #import "RegExCategories.h" 9 | 10 | @interface NSString_Replace : XCTestCase @end 11 | 12 | 13 | @implementation NSString_Replace 14 | 15 | - (void) test_replace_with_string_can_be_called_on_NSString 16 | { 17 | NSString* result = [@"ruff ruf!" replace:RX(@"ruf+") with:@"meow"]; 18 | XCTAssertEqualObjects(result, @"meow meow!", @"Expected to replace both barks."); 19 | } 20 | 21 | - (void) test_replace_with_block_can_be_called_on_NSString 22 | { 23 | NSString* result = [@"i love COW" replace:RX(@"[A-Z]+") withBlock:^(NSString* match){ return @"lamp"; }]; 24 | XCTAssertEqualObjects(result, @"i love lamp", @"Result should be 'I love lamp'."); 25 | } 26 | 27 | - (void) test_replace_with_details_block_can_be_called_on_NSString 28 | { 29 | NSString* result = [@"hi bud" replace:RX(@"\\w+") withDetailsBlock:^(RxMatch* match){ return [NSString stringWithFormat:@"%lu", match.value.length]; }]; 30 | XCTAssertEqualObjects(result, @"2 3", @"Result should be '2 3'."); 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-CategoriesTests/NSString+Split.m: -------------------------------------------------------------------------------- 1 | // 2 | // Split.m 3 | // Objective-C-Regex-Categories 4 | // 5 | 6 | 7 | #import 8 | #import "RegExCategories.h" 9 | 10 | @interface NSString_Split : XCTestCase @end 11 | 12 | 13 | @implementation NSString_Split 14 | 15 | - (void) test_split_can_be_called_from_an_NSString 16 | { 17 | NSArray* pieces = [@"I like cats,dogs" split:RX(@"[ ,]")]; 18 | XCTAssertEqual(pieces.count, 4U, @"Expected array to contain 4 items."); 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-CategoriesTests/Objective-C-Regex-CategoriesTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.bendytree.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TestProject/Objective-C-Regex-CategoriesTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Objective-C RegEx Categories 2 | 3 | Make Regular Expressions Easier in Objective-C 4 | 5 | - [Introduction](#introduction) 6 | - [Getting Started](#gettingstarted) 7 | - [Cocoa Pods](#gettingstarted-cocoapods) 8 | - [Download](#gettingstarted-download) 9 | - [Swift Support](#gettingstarted-swift) 10 | - [Quick Examples](#examples) 11 | - [Documentation](#documentation) 12 | - [Macros](#macros) 13 | - [Creation](#creation) 14 | - [Test If Match](#ismatch) 15 | - [Index Of Match](#indexof) 16 | - [Split](#split) 17 | - [First Match](#firstmatch) 18 | - [Matches](#matches) 19 | - [Replace](#replace) 20 | - [RxMatch Objects](#rxmatch) 21 | - [Support](#support) 22 | - [Licensing](#licensing) 23 | - [Testing](#testing) 24 | - [Alternatives](#alternatives) 25 | - [Who Uses It](#whousesit) 26 | 27 | 28 | 29 | ## Introduction 30 | 31 | This project simplifies regular expressions in Objective-C and Swift. 32 | 33 | As of iOS 4 (and OSX 10.7), [`NSRegularExpression`](https://developer.apple.com/library/Mac/DOCUMENTATION/Foundation/Reference/NSRegularExpression_Class/Reference/Reference.html) is built-in to [Foundation.framework](https://developer.apple.com/library/Mac/DOCUMENTATION/Cocoa/Reference/Foundation/ObjC_classic/_index.html#//apple_ref/doc/uid/20001091). The syntax is somewhat cumbersome and it leaves much of the work to you, so this library creates categories and macros to simplify usage of `NSRegularExpression`. 34 | 35 | Here is an example where four lines of code become one: 36 | 37 | ```objc 38 | // Without this library 39 | NSString* string = @"I have 2 dogs."; 40 | NSRegularExpression *regex = [NSRegularExpression regular ExpressionWithPattern:@"\\d+" options:NSRegularExpressionCaseInsensitive error:&error]; 41 | NSTextCheckingResult *match = [regex firstMatchInString:string options:0 range:NSMakeRange(0, [string length])]; 42 | BOOL isMatch = match != nil; 43 | 44 | // With this library 45 | BOOL isMatch = [@"I have 2 dogs." isMatch:RX(@"\\d+")]; 46 | ``` 47 | 48 | **TIP:** Refer to the header ([RegExCategories.h](https://github.com/bendytree/Objective-C-RegEx-Categories/blob/master/RegExCategories.h)) for more details and examples. 49 | 50 | 51 | ## Getting Started 52 | 53 | This library has no dependencies and works for iOS 4+ and OSX 10.7+. 54 | 55 | 56 | ### Installing Via CocoaPods 57 | 58 | If you use [Cocoa Pods](http://cocoapods.org/), then just add the following to your `Podfile`. Then run `pod install` from the command line. 59 | 60 | pod 'RegExCategories', '~> 1.0' 61 | 62 | 63 | ### Installing Via Download 64 | 65 | You can also just copy these two files into your project: 66 | 67 | - [RegExCategories.h](https://github.com/bendytree/Objective-C-RegEx-Categories/blob/master/RegExCategories.h) 68 | - [RegExCategories.m](https://github.com/bendytree/Objective-C-RegEx-Categories/blob/master/RegExCategories.m) 69 | 70 | You may want to add it to your [AppName]-Prefix.pch so that is is available across your code base. 71 | 72 | ```objc 73 | #ifdef __OBJC__ 74 | /* ...other references... */ 75 | #import "RegExCategories.h" 76 | #endif 77 | ``` 78 | 79 | You also need to have [ARC](https://developer.apple.com/library/ios/documentation/DeveloperTools/Conceptual/WhatsNewXcode/Articles/xcode_4_2.html) enabled on your Xcode project. If you don't then add the `-fobjc-arc` flag on `RegExCategories.m` under Targets > Build Phases > Compile Sources ([more info](http://stackoverflow.com/a/19925947/193896)). 80 | 81 | 82 | ### Swift Support 83 | 84 | You can use these extensions in [Swift](https://developer.apple.com/swift/). There are [additional steps](http://michal.codes/integrating-cocoapods-with-a-swift-project/) you must take to use Objective-C code from Swift. Once you've installed via Cocoa Pods or via download, do the following: 85 | 86 | 1. Add a Bridging Header 87 | 88 | This file will allow you to use objective-c code from your `.swift` code. Create an objective-c header file named [YourProjectName]-Bridging-Header.h 89 | 90 | 2. Configure The Bridging Header 91 | 92 | In the `Build Settings` of your main project, scroll down to the "Swift Compiler - Code Generation" section. In "Objective-C Bridging Header" add your file. Typically your value will be `[YourProjectName]/[YourProjectName]-Bridging-Header.h`. Build your project - if you have errors then you've set the wrong path. 93 | 94 | 3. Import RegExCategories 95 | 96 | In your bridging header, import the header for this library. For example, if you use Cocoa Pods it may look like this: 97 | 98 | #import 99 | 100 | If you just copied the files in to your project, it may look like this: 101 | 102 | #import "RegExCategories.h" 103 | 104 | 105 | 106 | ## Quick Examples 107 | 108 | Here are some short examples of how you might use the code. The [documentation](#documentation) section below goes into full detail. 109 | 110 | 111 | ```objc 112 | //Create an NSRegularExpression 113 | Rx* rx = RX(@"\\d"); 114 | Rx* rx = [Rx rx:@"\\d"]; 115 | Rx* rx = [Rx rx:@"\\d" ignoreCase:YES]; 116 | 117 | //Test if a string matches 118 | BOOL isMatch = [@"2345" isMatch:RX(@"^\\d+$")]; 119 | 120 | //Get first match 121 | NSString* age = [@"My dog is 3." firstMatch:RX(@"\\d+")]; 122 | 123 | //Get matches as a string array 124 | NSArray* words = [@"Hey pal" matches:RX(@"\\w+")]; 125 | // words => @[ @"Hey", @"pal" ] 126 | 127 | //Get first match with details 128 | RxMatch* match = [@"12.34, 56.78" firstMatchWithDetails:RX(@"\\d+([.]\\d+)")]; 129 | // match.value => @"12.34" 130 | // match.range => NSRangeMake(0, 5); 131 | // match.original => @"12.34, 56.78"; 132 | // match.groups => @[ RxMatchGroup, RxMatchGroup ]; 133 | 134 | //Replace with a template string 135 | NSString* result = [@"My dog is 12." replace:RX(@"\\d+") with:@"old"]; 136 | // result => @"My dog is old." 137 | 138 | //Replace with a block 139 | NSString* result = [RX(@"\\w+") replace:@"hi bud" withBlock:^(NSString* match){ 140 | return [NSString stringWithFormat:@"%i", match.length]; 141 | }]; 142 | // result => @"2 3" 143 | 144 | //Replace with a block that has the match details 145 | NSString* result = [RX(@"\\w+") replace:@"hi bud" withDetailsBlock:^(RxMatch* match){ 146 | return [NSString stringWithFormat:@"%i", match.value.length]; 147 | }]; 148 | // result => @"2 3" 149 | ``` 150 | 151 | You can also use the extensions in Swift, though macros are not available. Most examples are in Objective-C but here are some Swift examples: 152 | 153 | ```swift 154 | //Create an NSRegularExpression 155 | var rx = NSRegularExpression(pattern:"\\d"); 156 | var rx = NSRegularExpression.rx("\\d", ignoreCase:true); 157 | var rx = NSRegularExpression.rx("\\d", options: .CaseInsensitive); 158 | 159 | //Test if Matches a String 160 | var isMatch = rx.isMatch("3 dogs"); 161 | ``` 162 | 163 | 164 | ## Documentation 165 | 166 | 167 | ## Macros 168 | 169 | First off, we create an alias for NSRegularExpression named `Rx`. So instead of writing `NSRegularExpression` you can now use `Rx`. (this can be disabled - read on) 170 | 171 | ```objc 172 | //this 173 | NSRegularExpression* rx = [[NSRegularExpression alloc] initWithPattern:@"\\d"]; 174 | 175 | //can be written as 176 | Rx* rx = [[Rx alloc] initWithPattern:@"\\d"]; 177 | ``` 178 | 179 | We've also created a macro `RX()` for quick regex creation. Just pass a string and an `NSRegularExpression` object is created: 180 | 181 | ```objc 182 | //this 183 | NSRegularExpression* rx = [[NSRegularExpression alloc] initWithPattern:@"\\d"]; 184 | 185 | //can be written as 186 | Rx* rx = RX(@"\\d"); 187 | ``` 188 | 189 | These macros can be disabled by defining `DisableRegExCategoriesMacros` before you include the script. For example: 190 | 191 | ```objc 192 | #define DisableRegExCategoriesMacros 193 | #include "RegExCategories.h" 194 | ``` 195 | 196 | 197 | ## Creation 198 | 199 | Here are a few convenient ways to create an `NSRegularExpression`. 200 | 201 | ######Class Method - rx 202 | 203 | Rx* rx = [Rx rx:@"\\d+"]; 204 | 205 | ######Class Method - ignore case 206 | 207 | Rx* rx = [Rx rx:@"\\d+" ignoreCase:YES]; 208 | 209 | ######Class Method - with options 210 | 211 | Rx* rx = [Rx rx:@"\\d+" options:NSRegularExpressionCaseInsensitive]; 212 | 213 | ######Init With Pattern 214 | 215 | Rx* rx = [[Rx alloc] initWithPattern:@"\d+"]; 216 | 217 | ######String Extension 218 | 219 | Rx* rx = [@"\\d+" toRx]; 220 | 221 | ######String Extension - ignore case 222 | 223 | Rx* rx = [@"\\d+" toRxIgnoreCase:YES]; 224 | 225 | ######String Extension - with options 226 | 227 | Rx* rx = [@"\\d+" toRxWithOptions:NSRegularExpressionCaseInsensitive]; 228 | 229 | 230 | 231 | ##Test If Match 232 | 233 | Tests whether a regular expression matches a string. 234 | 235 | ######From NSRegularExpression 236 | 237 | BOOL isMatch = [RX(@"\\d+") isMatch:@"Dog #1"]; 238 | // => true 239 | 240 | ######From NSString 241 | 242 | BOOL isMatch = [@"Dog #1" isMatch:RX(@"\\d+")]; 243 | // => true 244 | 245 | 246 | 247 | ##Index Of Match 248 | 249 | Get the character index of the first match. If no match is found, then `-1`. 250 | 251 | ######From NSRegularExpression 252 | 253 | int index = [RX(@"\\d+") indexOf:@"Buy 1 dog or buy 2?"]; 254 | // => 4 255 | 256 | int index = [RX(@"\\d+") indexOf:@"Buy a dog?"]; 257 | // => -1 258 | 259 | ######From NSString 260 | 261 | int index = [@"Buy 1 dog or buy 2?" indexOf:RX(@"\\d+")]; 262 | // => 4 263 | 264 | int index = [@"Buy a dog?" indexOf:RX(@"\\d+")]; 265 | // => -1 266 | 267 | 268 | 269 | ##Split A String 270 | 271 | Split an NSString using a regex as the delimiter. The result is an NSArray of NSString objects. 272 | 273 | ######From NSRegularExpression 274 | 275 | NSArray* pieces = [RX(@"[ ,]") split:@"A dog,cat"]; 276 | // => @[@"A", @"dog", @"cat"] 277 | 278 | 279 | ######From NSString 280 | 281 | NSArray* pieces = [@"A dog,cat" split:RX(@"[ ,]")]; 282 | // => @[@"A", @"dog", @"cat"] 283 | 284 | Empty results are not removed. For example: 285 | 286 | NSArray* pieces = [@",a,,b," split:RX(@"[,]")]; 287 | // => @[@"", @"a", @"", @"b", @""] 288 | 289 | 290 | 291 | ##First Match 292 | 293 | Get the first match as an `NSString`. If no match is found, nil is returned. 294 | 295 | ###### First Match from NSString 296 | 297 | NSString* match = [@"55 or 99 spiders" firstMatch:RX(@"\\d+")]; 298 | // => @"55" 299 | 300 | NSString* match = [@"A lot of spiders" firstMatch:RX(@"\\d+")]; 301 | // => nil 302 | 303 | ###### First Match from NSRegularExpression 304 | 305 | NSString* match = [RX(@"\\d+") firstMatch:@"55 or 99 spiders"]; 306 | // => @"55" 307 | 308 | ###### First Match With Details (from NSString or NSRegularExpression) 309 | 310 | If you want more details about the match (such as the range or captured groups), then use match with details. It returns an [RxMatch](#rxmatch) object if a match is found, otherwise nil. 311 | 312 | RxMatch* match = [@"55 or 99 spiders" firstMatchWithDetails:RX(@"\\d+")]; 313 | // => { value: @"55", range:NSRangeMake(0, 2), groups:[RxMatchGroup, ...] } 314 | 315 | RxMatch* match = [@"A lot of spiders" firstMatchWithDetails:RX(@"\\d+")]; 316 | // => nil 317 | 318 | RxMatch* match = [RX(@"\\d+") firstMatchWithDetails:@"55 or 99 spiders"]; 319 | // => { value: @"55", range:NSRangeMake(0, 2), groups:[RxMatchGroup, ...] } 320 | 321 | 322 | 323 | 324 | ##Matches 325 | 326 | 327 | ###### Matches (from NSString or NSRegularExpression) 328 | 329 | Matches returns all matches as an `NSArray`, each as an `NSString`. If no matches are found, the `NSArray` is empty. 330 | 331 | NSArray* matches = [@"55 or 99 spiders" matches:RX(@"\\d+")]; 332 | // => @[ @"55", @"99" ] 333 | 334 | NSArray* matches = [RX(@"\\d+") matches:@"55 or 99 spiders"]; 335 | // => @[ @"55", @"99" ] 336 | 337 | 338 | ###### Matches With Details (from NSString or NSRegularExpression) 339 | 340 | Matches with details returns all matches as an `NSArray`, each object is an [RxMatch](#rxmatch) object. 341 | 342 | NSArray* matches = [@"55 or 99 spiders" matchesWithDetails:RX(@"\\d+")]; 343 | // => @[ RxMatch, RxMatch ] 344 | 345 | NSArray* matches = [RX(@"\\d+") matchesWithDetails:@"55 or 99 spiders"]; 346 | // => @[ RxMatch, RxMatch ] 347 | 348 | 349 | 350 | ##Replace 351 | 352 | ###### Replace With Template 353 | 354 | Replace allows you to replace matched substrings with a templated string. 355 | 356 | NSString* result = [RX(@"ruf+") replace:@"ruf ruff!" with:@"meow"]; 357 | // => @"meow meow!" 358 | 359 | ###### Replace With Block 360 | 361 | Replace with block lets you pass an objective-c block that returns the replacement `NSString`. The block receives an `NSString` which is the matched substring. 362 | 363 | NSString* result = [RX(@"[A-Z]+") replace:@"i love COW" withBlock:^(NSString*){ return @"lamp"; }]; 364 | // => @"i love lamp" 365 | 366 | 367 | ###### Replace With Details Block 368 | 369 | Similar to replace with block, but this block receives an [RxMatch](#rxmatch) for each match. This gives you details about the match such as captured groups. 370 | 371 | NSString* result = [RX(@"\\w+") replace:@"two three" withDetailsBlock:^(RxMatch* match){ 372 | return [NSString stringWithFormat:@"%i", match.value.length]; 373 | }]; 374 | // => @"3 5" 375 | 376 | 377 | ###### Replace From NSString 378 | 379 | Replace can also be called from an `NSString`. 380 | 381 | NSString* result = [@"ruf ruff!" replaceRX(@"ruf+") with:@"meow"]; 382 | // => @"meow meow!" 383 | 384 | NSString* result = [@"i love COW" replace:RX(@"[A-Z]+") withBlock:^(NSString*){ return @"lamp"; }]; 385 | // => @"i love lamp" 386 | 387 | NSString* result = [@"two three" replace:RX(@"\\w+") withDetailsBlock:^(RxMatch* match){ 388 | return [NSString stringWithFormat:@"%i", match.value.length]; 389 | }]; 390 | // => @"3 5" 391 | 392 | 393 | 394 | ## RxMatch Objects 395 | 396 | `RxMatch` and `RxMatchGroup` are objects that contain information about a match and its groups. 397 | 398 | ```objc 399 | @interface RxMatch : NSObject 400 | 401 | /* The substring that matched the expression. */ 402 | @property (retain) NSString* value; 403 | 404 | /* The range of the original string that was matched. */ 405 | @property (assign) NSRange range; 406 | 407 | /* Each object is an RxMatchGroup. */ 408 | @property (retain) NSArray* groups; 409 | 410 | /* The full original string that was matched against. */ 411 | @property (retain) NSString* original; 412 | 413 | @end 414 | 415 | @interface RxMatchGroup : NSObject 416 | 417 | /* The substring matched for the group. */ 418 | @property (retain) NSString* value; 419 | 420 | /* The range of the captured group, relative to the original string. */ 421 | @property (assign) NSRange range; 422 | 423 | @end 424 | ``` 425 | 426 | 427 | 428 | ## Support 429 | 430 | If you need help, [submit an issue](https://github.com/bendytree/Objective-C-RegEx-Categories/issues) or send a [pull request](https://github.com/bendytree/Objective-C-RegEx-Categories/pulls). Please include appropriate unit tests in any pull requests. 431 | 432 | You can visit my website at [joshwright.com](http://joshwright.com) or tweet at me [@BendyTree](http://twitter.com/bendytree). 433 | 434 | 435 | 436 | ## Licensing 437 | 438 | [MIT License](https://github.com/bendytree/Objective-C-RegEx-Categories/blob/master/LICENSE.txt) - do whatever you want, just (1) provide attribution and (2) don't hold me liable. 439 | 440 | 441 | 442 | ## Testing 443 | 444 | This repository includes unit tests written in the [XCTest](https://developer.apple.com/library/ios/documentation/ToolsLanguages/Conceptual/Xcode_Overview/UnitTestYourApp/UnitTestYourApp.html) framework. 445 | 446 | 447 | 448 | ## Alternatives 449 | 450 | There are a few other options for using regular expressions in objective-c including: 451 | 452 | - Raw [NSRegularExpression](https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSRegularExpression_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40009708) - Built in to Foundation since OSX 10.7 and iOS 4.0. 453 | - [RegexKitLite](http://regexkit.sourceforge.net/RegexKitLite/) - Bridge between NSString and [ICU Regex](http://site.icu-project.org/) 454 | - [CocoaRegex](https://github.com/psychs/cocoaregex) - Alternative bridge to ICU 455 | 456 | 457 | 458 | 459 | ## Who Uses It? 460 | 461 | Here is a list of projects using Objective-C RegEx Categories. If you're using it, [tweet at me](http://twitter.com/bendytree) (@BendyTree) and I'll add you to the list: 462 | 463 | - [Memorize Anything](https://itunes.apple.com/us/app/memorize-anything/id430219093?ls=1&mt=8) 464 | - [SpeakY](https://itunes.apple.com/us/app/speaky-instant-sound-system/id654845699?ls=1&mt=8) 465 | - [Oyster 1.2](http://www.rwe-uk.com/site/comments/oyster_1.2_the_mac_regex_tool_released) 466 | - [Loaf — Recipes for Easy Cooking](https://geo.itunes.apple.com/gb/app/loaf-recipes-for-easy-cooking/id956511078?mt=8) 467 | --------------------------------------------------------------------------------