Sign in Join
Wiki copytemporary

macOS NSRegularExpression: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: macOS NSRegularExpression

OldNewLine
1+## Regular expressions
2+
3+Regular expressions are patterns used to match specified alpha-numeric character combinations in the string data being searched.
4+
5+Each character in a regular expression (that is, each character in the string describing its pattern) is either a metacharacter or operator, having a special meaning, or a regular character that has a literal meaning.
6+
7+Regular expressions can be incredibly complex. Indeed, whole books have been written about them! For a gentle introduction to regular expressions, see this [O'Reilly article](https://www.oreilly.com/content/an-introduction-to-regular-expressions/).
8+
9+## NSRegularExpression Overview
10+
11+The NSRegularExpression class has convenience methods for returning all the matches as an array, the total number of matches, the first match, and the range of the first match.
12+
13+An individual match is represented by an instance of the NSTextCheckingResult class, which carries information about the overall matched range (via its range property), and the range of each individual capture group (via the rangeAtIndex method).
14+
15+NSRegularExpression conforms to the International Components for Unicode (ICU) specification for regular expressions.
16+
17+### Metacharacters
18+
19+For a comprehensive list of characters used by the NSRegularExpression class that have a special meaning in regular expression patterns, see the [ICU listing](https://unicode-org.github.io/icu/userguide/strings/regexp.html#regular-expression-metacharacters).
20+
21+### Operators
22+
23+For a comprehensive list of operators used by the NSRegularExpression class, see the [ICU listing](https://unicode-org.github.io/icu/userguide/strings/regexp.html#regular-expression-operators).
24+
25+## Example 1 - match a pattern
26+
27+In this fairly trivial and contrived code example, we use the **\d** metacharacter which matches a decimal digit and the **+** operator to match the preceding decimal digit one or more times. This pattern **\d+** aims to match all the occurrences of numbers in the search string which we then output using NSLog(). It uses the NSRegularExpression convenience methods for returning all of the matches in the search string as an array and the total number of matches.
28+
29+### Code
30+
31+``` pascal
32+Program regex_ex1;
33+
34+{$mode objfpc}{$H+}
35+{$modeswitch objectivec2}
36+
37+Uses
38+ MacOSAll, CocoaAll, SysUtils;
39+
40+Var
41+ srchStr : String;
42+ patnStr : String;
43+ myRegex : NSregularExpression;
44+ matches : NSArray;
45+ match : NSTextCheckingResult;
46+ error : NSErrorPtr;
47+
48+Begin
49+ error := Nil;
50+ srchStr := 'I have 43 bags of 60 marbles.';
51+ patnStr := '\d+';
52+
53+ // Create a regular expression with given string and options
54+ myRegex := NSregularExpression.regularExpressionWithPattern_options_error(NSStr(patnStr), NSRegularExpressionOptions(NSRegularExpressionCaseInsensitive), error);
55+
56+ // Check creation of regular expression with given string and options
57+ if(error <> Nil) then
58+ begin
59+ NSLog(NSStr('Regex creation error: %@'), error);
60+ Exit;
61+ end;
62+
63+ // Save any matches in the given string in the matches array
64+ matches := myRegex.matchesInString_options_range(NSStr(srchStr), 0, NSMakeRange(0, srchStr.Length));
65+
66+ // Output
67+ NSLog(NSStr('Search string: %@'), NSStr(srchStr));
68+ NSLog(NSStr('Pattern string: %@'), NSStr(patnStr));
69+ NSLog(NSStr('Number of matches: %lu'), myRegex.numberOfMatchesInString_options_range(NSStr(srchStr), 0, NSMakeRange(0, srchStr.Length)));
70+
71+ for match in matches do
72+ NSLog(NSStr('match: %@'), NSStr(srchStr).substringWithRange(match.rangeAtIndex(0)));
73+End.
74+```
75+
76+### Output
77+
78+The output from running the above code example is:
79+
80+`2021-06-12 21:05:46.335 regex_ex1[26138:232243] Search string: I have 43 bags of 60 marbles.`\
81+`2021-06-12 21:05:46.336 regex_ex1[26138:232243] Pattern string: \d+`\
82+`2021-06-12 21:05:46.336 regex_ex1[26138:232243] Number of matches: 2`\
83+`2021-06-12 21:05:46.336 regex_ex1[26138:232243] match: 43`\
84+`2021-06-12 21:05:46.336 regex_ex1[26138:232243] match: 60`
85+
86+### Code explanation
87+
88+The call to *regularExpressionWithPattern_options_error()* creates an NSRegularExpression object instance (myRegex) with the specified regular expression pattern and options.
89+
90+Options are specified using *NSRegularExpressionOptions()*. Note that by default NSRegularExpression performs case-sensitive searches, so we specified the NSRegularExpressionCaseInsensitive option for case-insenstive searches although, because we are dealing with digits above, this has no effect and we might as well have specified Nil in this example for no options.
91+
92+Once we have the NSRegularExpression object, we can then use it for matching text among other operations.
93+
94+After checking that the creation of the regular expression did not fail with an error, we call the *matchesInString_options_range()* method to search for any matches and store them in our NSArray (matches). This method takes our search string, any options (there are none here) and the range to search. The range is specified by giving *NSMakeRange()* the starting location to search in the string (0 = the beginning of the string) and the length of the search string.
95+
96+Next, we output the search string and pattern string, and then call the *numberOfMatchesInString_options_range()* method to determine the number of matches and output it.
97+
98+Finally, we iterate through the matches NSArray and output the matches individually. The call to *rangeAtIndex(0)* is the full match and is equivalent to simply calling *range*. The code for this looks a little obscure. Let me try to unpack it for you.
99+
100+If you just output the content of the matches NSArray you get this:
101+
102+`"<NSSimpleRegularExpressionCheckingResult: 0x14d617570>{7, 2}{<NSRegularExpression: 0x14d614fd0> \\d+ 0x1}",`\
103+`"<NSSimpleRegularExpressionCheckingResult: 0x14d617610>{18, 2}{<NSRegularExpression: 0x14d614fd0> \\d+ 0x1}"`
104+
105+Notice the {7, 2} and {18, 2} ranges which locate the first number at position 7 (counting from zero) in the search string with a length of 2 and the second number at position 18 with a length of 2. Knowing those ranges, you could use:
106+
107+``` pascal
108+NSLog(NSStr('match: ''%@'''), NSStr(srchStr).substringWithRange(NSMakeRange(7, 2)));
109+```
110+
111+to output the first number. The *substringWithRange()* method extracts from our search string the substring that matches the specified range (7, 2). Clearer than mud? I hope so.
112+
113+## Example 2 - match pattern groups
114+
115+This is similar to Example 1 above, except that this time we match groups of characters. Our search string is the same as before, but our pattern string has some added complexity. The pattern matches a decimal digit one or more times as before, but this time as a group which is delineated by using parentheses: **(/d+)**. Next, we use a point **.** to match any character and **?** to match zero or one times following the digit(s). Finally, we match the set of characters from a to z **(\[a-z\]+)** one or more times as a group.
116+
117+### Code
118+
119+``` pascal
120+Program regex_ex2;
121+
122+{$mode objfpc}{$H+}
123+{$modeswitch objectivec2}
124+
125+Uses
126+ MacOSAll, CocoaAll, SysUtils;
127+
128+Var
129+ srchStr : String;
130+ patnStr : String;
131+ myRegex : NSregularExpression;
132+ matches : NSArray;
133+ match : NSTextCheckingResult;
134+ error : NSErrorPtr;
135+
136+Begin
137+ error := Nil;
138+ srchStr := 'I have 43 Bags of 60 Marbles.';
139+ patnStr := '(\d+).?([a-z]+)';
140+
141+ // Create a regular expression with given string and options
142+ myRegex := NSregularExpression.regularExpressionWithPattern_options_error(NSStr(patnStr), NSRegularExpressionOptions(NSRegularExpressionCaseInsensitive), error);
143+
144+ // Check creation of regular expression with given string and options
145+ if(error <> Nil) then
146+ begin
147+ NSLog(NSStr('Regex creation error: %@'), error);
148+ Exit;
149+ end;
150+
151+ // Save any matches in the given string in the matches array
152+ matches := myRegex.matchesInString_options_range(NSStr(srchStr), 0, NSMakeRange(0, srchStr.Length));
153+
154+ // Output
155+ NSLog(NSStr('Search string: %@'), NSStr(srchStr));
156+ NSLog(NSStr('Pattern string: %@'), NSStr(patnStr));
157+ NSLog(NSStr('Number of matches: %lu'), myRegex.numberOfMatchesInString_options_range(NSStr(srchStr), 0, NSMakeRange(0, srchStr.Length)));
158+
159+ for match in matches do
160+ begin
161+ NSLog(NSStr('match(0): ''%@'''), NSStr(srchStr).substringWithRange(match.rangeAtIndex(0)));
162+ NSLog(NSStr('match(1): ''%@'''), NSStr(srchStr).substringWithRange(match.rangeAtIndex(1)));
163+ NSLog(NSStr('match(2): ''%@'''), NSStr(srchStr).substringWithRange(match.rangeAtIndex(2)));
164+ end;
165+End.
166+```
167+
168+### Output
169+
170+`2021-06-14 17:39:51.255 program2[7376:149163] Search string: I have 43 bags of 60 marbles.`\
171+`2021-06-14 17:39:51.255 program2[7376:149163] Pattern string: (\d+).?([a-z]+)`\
172+`2021-06-14 17:39:51.255 program2[7376:149163] Number of matches: 2`\
173+`2021-06-14 17:39:51.255 program2[7376:149163] match(0): '43 Bags'`\
174+`2021-06-14 17:39:51.255 program2[7376:149163] match(1): '43'`\
175+`2021-06-14 17:39:51.255 program2[7376:149163] match(2): 'Bags'`\
176+`2021-06-14 17:39:51.255 program2[7376:149163] match(0): '60 Marbles'`\
177+`2021-06-14 17:39:51.255 program2[7376:149163] match(1): '60'`\
178+`2021-06-14 17:39:51.255 program2[7376:149163] match(2): 'Marbles'`
179+
180+### Code explanation
181+
182+The explanation is pretty much the same as for Example 1, except that:
183+
184+1\) The NSRegularExpressionCaseInsensitive option for case-insenstive searches now has a use. We specified the set of lowercase characters a to z, but because of the option we matched the words Bags and Marbles with initial capital letters.
185+
186+2\) The call to *rangeAtIndex(0)* or the equivalent *range* matches the full pattern; the call to *rangeAtIndex(1)* matches the first group of digits that we specified; and the call to *rangeAtIndex(2)* matches the second group of characters that we specified.
187+
188+## Example 3 - replace matched pattern
189+
190+This is similar to Example 1 above, except that this time we replace the decimal numbers matched by the regular expression with words instead of numbers. This is done two ways: one uses the *stringByReplacingMatchesInString_options_range_withTemplate()* method which returns a new string with the replacements and the other uses the *replaceMatchesInString_options_range_withTemplate()* method which replaces the matches in the original search string.
191+
192+### Code
193+
194+``` pascal
195+Program regex_ex3;
196+
197+{$mode objfpc}{$H+}
198+{$modeswitch objectivec2}
199+
200+Uses
201+ MacOSAll, CocoaAll, SysUtils;
202+
203+Var
204+ srchStr : NSString;
205+ srchStr2: NSMutableString;
206+ patnStr : NSString;
207+ templStr: NSString;
208+ myRegex : NSregularExpression;
209+ matches : NSArray;
210+ match : NSTextCheckingResult;
211+ numMatch: NSInteger;
212+ error : NSErrorPtr;
213+ count : ShortInt;
214+
215+Begin
216+ error := Nil;
217+ srchStr := NSStr('I have 43 Bags of 60 Marbles.');
218+ srchStr2:= NSMutableString.stringWithString(srchStr);
219+ patnStr := NSStr('\d+');
220+
221+ // Create a regular expression with given string and options
222+ myRegex := NSregularExpression.regularExpressionWithPattern_options_error(patnStr, NSRegularExpressionOptions(NSRegularExpressionCaseInsensitive), error);
223+
224+ // Check creation of regular expression with given string and options
225+ if(error <> Nil) then
226+ begin
227+ NSLog(NSStr('Regex creation error: %@'), error);
228+ Exit;
229+ end;
230+
231+ // Save any matches in the given string in the matches array
232+ matches := myRegex.matchesInString_options_range(srchStr, 0, NSMakeRange(0, srchStr.Length));
233+
234+ // Save number of matches
235+ numMatch := myRegex.numberOfMatchesInString_options_range(srchStr, 0, NSMakeRange(0, srchStr.Length));
236+
237+ // Output
238+ NSLog(NSStr('Search string: %@'), srchStr);
239+ NSLog(NSStr('Pattern string: %@'), patnStr);
240+ NSLog(NSStr('Number of matches: %lu'), numMatch);
241+
242+ // Alternative 1: Using stringByReplacingMatchesInString_options_range_withTemplate()
243+ for count := numMatch downto 1 do
244+ begin
245+ for match in matches do
246+ begin
247+ if(srchStr.substringWithRange(match.range) = NSStr('43')) then
248+ begin
249+ templStr := NSStr('forty-three');
250+ NSLog(NSStr('Match 1: %@ - Template string: %@'), srchStr.substringWithRange(match.range), templStr);
251+ end;
252+
253+ if(srchStr.substringWithRange(match.range) = NSStr('60')) then
254+ begin
255+ templStr := NSStr('sixty');
256+ NSLog(NSStr('Match 2: %@ - Template string: %@'), srchStr.substringWithRange(match.range), templStr);
257+ end;
258+
259+ // Do string replacement
260+ srchStr := myRegex.stringByReplacingMatchesInString_options_range_withTemplate(srchStr, NSRegularExpressionOptions(Nil), match.range, templStr);
261+ end;
262+
263+ // Save any matches in the new search string in the matches array
264+ matches := myRegex.matchesInString_options_range(srchStr, 0, NSMakeRange(0, srchStr.Length));
265+ end;
266+
267+ // Output string with replacements
268+ NSLog(NSStr('Result: %@'), srchStr);
269+
270+ // Save any matches in the given string in the matches array
271+ matches := myRegex.matchesInString_options_range(srchStr2, 0, NSMakeRange(0, srchStr2.Length));
272+
273+ // Alternative 2: Using replaceMatchesInString_options_range_withTemplate()
274+ for count := numMatch downto 1 do
275+ begin
276+ for match in matches do
277+ begin
278+ if(srchStr2.substringWithRange(match.range) = NSStr('43')) then
279+ begin
280+ templStr := NSStr('forty-three');
281+ NSLog(NSStr('Match 1: %@ - Template string: %@'), srchStr2.substringWithRange(match.range), templStr);
282+ end;
283+ if(srchStr2.substringWithRange(match.range) = NSStr('60')) then
284+ begin
285+ templStr := NSStr('sixty');
286+ NSLog(NSStr('Match 2: %@ - Template string: %@'), srchStr2.substringWithRange(match.range), templStr);
287+ end;
288+
289+ // Do string replacement
290+ myRegex.replaceMatchesInString_options_range_withTemplate(srchStr2, NSRegularExpressionOptions(Nil), match.range, templStr);
291+ end;
292+
293+ // Save any matches in the new search string in the matches array
294+ matches := myRegex.matchesInString_options_range(srchStr2, 0, NSMakeRange(0, srchStr2.Length));
295+ end;
296+
297+ // Output string with replacements
298+ NSLog(NSStr('Result: %@'), srchStr2);
299+End.
300+```
301+
302+### Output
303+
304+`2021-06-14 22:42:55.807 program3[9115:227765] Search string: I have 43 Bags of 60 Marbles.`\
305+`2021-06-14 22:42:55.808 program3[9115:227765] Pattern string: \d+`\
306+`2021-06-14 22:42:55.808 program3[9115:227765] Number of matches: 2`\
307+`2021-06-14 22:42:55.808 program3[9115:227765] Match 1: 43 - Template string: forty-three`\
308+`2021-06-14 22:42:55.808 program3[9115:227765] Match 2: 60 - Template string: sixty`\
309+`2021-06-14 22:42:55.808 program3[9115:227765] Result: I have forty-three Bags of sixty Marbles.`\
310+`2021-06-14 22:42:55.808 program3[9115:227765] Match 1: 43 - Template string: forty-three`\
311+`2021-06-14 22:42:55.808 program3[9115:227765] Match 2: 60 - Template string: sixty`\
312+`2021-06-14 22:42:55.808 program3[9115:227765] Result: I have forty-three Bags of sixty Marbles.`
313+
314+## Example 4 - rearrange matched pattern groups
315+
316+This is similar to Example 2 above, except that this time not only do we match groups of characters, we rearrange them. Our search string is different this time and represents a contact list. We are going to rearrange the format from Firstname, Lastname to the more sensible Lastname, Firstname and then enclose the contact number state area code in parentheses. To do this, our pattern string has some added complexity.
317+
318+The pattern matches the first word group as a group of word characters followed by a comma **(\w+),** and does exactly the same again for the second word group, and finally matches the two digits of the area code as a group **(\d{2})**. The curly braces enclosing the number **{2}** after the digit metacharacter specifies that we want to match exactly two digits which comprise the area code.
319+
320+The template string rearranges the data groups by switching the order of the first two word groups and adding parentheses around the third group which comprises the area code number.
321+
322+### Code
323+
324+``` pascal
325+Program regex_ex4;
326+
327+{$mode objfpc}{$H+}
328+{$modeswitch objectivec2}
329+
330+Uses
331+ MacOSAll, CocoaAll, SysUtils;
332+
333+Var
334+ srchStr : NSString;
335+ srchStr2: NSMutableString;
336+ patnStr : NSString;
337+ tmplStr : NSString;
338+ myRegex : NSregularExpression;
339+ matches : NSArray;
340+ match : NSTextCheckingResult;
341+ error : NSErrorPtr;
342+
343+Begin
344+ error := Nil;
345+ srchStr := NSStr('Firstname, Lastname, 02 9428 4687');
346+ srchStr2:= NSMutableString.stringWithString(srchStr);
347+ patnStr := NSStr('(\w+), (\w+), (\d{2})');
348+ tmplStr := NSStr('$2, $1, ($3)');
349+
350+ // Create a regular expression with given string and options
351+ myRegex := NSregularExpression.regularExpressionWithPattern_options_error(patnStr, NSRegularExpressionOptions(NSRegularExpressionCaseInsensitive), error);
352+
353+ // Check creation of regular expression with given string and options
354+ if(error <> Nil) then
355+ begin
356+ NSLog(NSStr('Regex creation error: %@'), error);
357+ Exit;
358+ end;
359+
360+ // Save any matches in the given string in the matches array
361+ matches := myRegex.matchesInString_options_range(srchStr, 0, NSMakeRange(0, srchStr.Length));
362+
363+ // Output
364+ NSLog(NSStr('Search string: %@'), srchStr);
365+ NSLog(NSStr('Pattern string: %@'), patnStr);
366+ NSLog(NSStr('Template string: %@'), tmplStr);
367+ NSLog(NSStr('Number of matches: %lu'), myRegex.numberOfMatchesInString_options_range(srchStr, 0, NSMakeRange(0, srchStr.Length)));
368+
369+ for match in matches do
370+ begin
371+ NSLog(NSStr('match(1): %@'), srchStr.substringWithRange(match.rangeAtIndex(1)));
372+ NSLog(NSStr('match(2): %@'), srchStr.substringWithRange(match.rangeAtIndex(2)));
373+ NSLog(NSStr('match(3): %@'), srchStr.substringWithRange(match.rangeAtIndex(3)));
374+ // Do the replacements
375+ myRegex.replaceMatchesInString_options_range_withTemplate(srchStr2, NSRegularExpressionOptions(Nil), match.range, tmplStr);
376+ end;
377+
378+ NSLog(NSStr('Result: %@'), srchStr2);
379+End.
380+```
381+
382+### Output
383+
384+`2021-06-15 20:37:54.880 program4[23076:159228] Search string: Firstname, Lastname, 02 9428 4687`\
385+`2021-06-15 20:37:54.880 program4[23076:159228] Pattern string: (\w+), (\w+), (\d{2})`\
386+`2021-06-15 20:37:54.880 program4[23090:159932] Template string: $2, $1, ($3)`\
387+`2021-06-15 20:37:54.881 program4[23076:159228] Number of matches: 1`\
388+`2021-06-15 20:37:54.881 program4[23076:159228] match(1): Firstname`\
389+`2021-06-15 20:37:54.881 program4[23076:159228] match(2): Lastname`\
390+`2021-06-15 20:37:54.881 program4[23076:159228] match(3): 02`\
391+`2021-06-15 20:37:54.881 program4[23076:159228] Result: Lastname, Firstname, (02) 9428 4687`
392+
393+## See also
394+
395+- [[macOS NSString Regular Expressions]].
396+- [[RegEx packages]] - cross platform.
397+
398+## External links
399+
400+- [Apple: NSRegularExpression](https://developer.apple.com/documentation/foundation/nsregularexpression).
401+
402+- [ICU: Documentation](https://unicode-org.github.io/icu/).
403+
404+---
405+*From the Free Pascal wiki, page "macOS NSRegularExpression", as it stood on 2021-08-07 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history