You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 9-regular-expressions/02-regexp-character-classes/article.md
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -41,7 +41,7 @@ Most used are:
41
41
: A digit: a character from `0` to `9`.
42
42
43
43
`pattern:\s` ("s" is from "space")
44
-
: A space symbol: includes spaces, tabs `\t`, newlines `\n` and few other rare characters:`\v`, `\f` and `\r`.
44
+
: A space symbol: includes spaces, tabs `\t`, newlines `\n` and few other rare characters, such as`\v`, `\f` and `\r`.
45
45
46
46
`pattern:\w` ("w" is from "word")
47
47
: A "wordly" character: either a letter of Latin alphabet or a digit or an underscore `_`. Non-Latin letters (like cyrillic or hindi) do not belong to `pattern:\w`.
Please note that in the word `subject:Exception` there's a substring `subject:xce`. It didn't match the pattern, because the letters are lowercase, while in the set `pattern:[0-9A-F]` they are uppercase.
45
+
Here `pattern:[0-9A-F]` has two ranges: it searches for a character that is either a digit from `0` to `9` or a letter from `A` to `F`.
46
46
47
-
If we want to find it too, then we can add a range `a-f`: `pattern:[0-9A-Fa-f]`. The `pattern:i` flag would allow lowercase too.
47
+
If we'd like to look for lowercase letters as well, we can add the range `a-f`: `pattern:[0-9A-Fa-f]`. Or add the flag `pattern:i`.
48
48
49
-
**Character classes are shorthands for certain character sets.**
49
+
We can also use character classes inside `[…]`.
50
50
51
+
For instance, if we'd like to look for a wordly character `pattern:\w` or a hyphen `pattern:-`, then the set is `pattern:[\w-]`.
52
+
53
+
Combining multiple classes is also possible, e.g. `pattern:[\s\d]` means "a space character or a digit".
54
+
55
+
```smart header="Character classes are shorthands for certain character sets"
51
56
For instance:
52
57
53
58
- **\d** -- is the same as `pattern:[0-9]`,
54
59
- **\w** -- is the same as `pattern:[a-zA-Z0-9_]`,
55
-
-**\s** -- is the same as `pattern:[\t\n\v\f\r ]` plus few other unicode space characters.
60
+
- **\s** -- is the same as `pattern:[\t\n\v\f\r ]`, plus few other rare unicode space characters.
61
+
```
62
+
63
+
### Example: multi-language \w
64
+
65
+
As the character class `pattern:\w` is a shorthand for `pattern:[a-zA-Z0-9_]`, it can't find Chinese hieroglyphs, Cyrillic letters, etc.
66
+
67
+
We can write a more universal pattern, that looks for wordly characters in any language. That's easy with unicode properties: `pattern:[\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}]`.
56
68
57
-
We can use character classes inside `[…]` as well.
69
+
Let's decipher it. Similar to `pattern:\w`, we're making a set of our own that includes characters with following unicode properties:
58
70
59
-
For instance, we want to match all wordly characters or a dash, for words like "twenty-third". We can't do it with `pattern:\w+`, because `pattern:\w` class does not include a dash. But we can use `pattern:[\w-]`.
71
+
-`Alphabetic` (`Alpha`) - for letters,
72
+
-`Mark` (`M`) - for accents,
73
+
-`Decimal_Number` (`Nd`) - for digits,
74
+
-`Connector_Punctuation` (`Pc`) - for the underscore `'_'` and similar characters,
75
+
-`Join_Control` (`Join_C`) - two special codes `200c` and `200d`, used in ligatures, e.g. in Arabic.
60
76
61
-
We also can use several classes, for example `pattern:[\s\S]` matches spaces or non-spaces -- any character. That's wider than a dot `"."`, because the dot matches any character except a newline (unless `pattern:s` flag is set).
77
+
An example of use:
78
+
79
+
```js run
80
+
let regexp =/[\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}]/gu;
81
+
82
+
let str =`Hi 你好 12`;
83
+
84
+
// finds all letters and digits:
85
+
alert( str.match(regexp) ); // H,i,你,好,1,2
86
+
```
87
+
88
+
Of course, we can edit this pattern: add unicode properties or remove them. Unicode properties are covered in more details in the article <info:regexp-unicode>.
89
+
90
+
```warn header="Unicode properties aren't supported in Edge and Firefox"
91
+
Unicode properties `pattern:p{…}` are not yet implemented in Edge and Firefox. If we really need them, we can use library [XRegExp](http://xregexp.com/).
92
+
93
+
Or just use ranges of characters in a language that interests us, e.g. `pattern:[а-я]` for Cyrillic letters.
94
+
```
62
95
63
96
## Excluding ranges
64
97
@@ -78,22 +111,20 @@ The example below looks for any characters except letters, digits and spaces:
78
111
alert( "alice15@gmail.com".match(/[^\d\sA-Z]/gi) ); // @ and .
79
112
```
80
113
81
-
## No escaping in […]
114
+
## Escaping in […]
82
115
83
-
Usually when we want to find exactly the dot character, we need to escape it like `pattern:\.`. And if we need a backslash, then we use `pattern:\\`.
116
+
Usually when we want to find exactly a special character, we need to escape it like `pattern:\.`. And if we need a backslash, then we use `pattern:\\`, and so on.
84
117
85
-
In square brackets the vast majority of special characters can be used without escaping:
118
+
In square brackets we can use the vast majority of special characters without escaping:
86
119
87
-
- A dot `pattern:'.'`.
88
-
- A plus `pattern:'+'`.
89
-
- Parentheses `pattern:'( )'`.
90
-
- Dash `pattern:'-'` in the beginning or the end (where it does not define a range).
91
-
- A caret `pattern:'^'` if not in the beginning (where it means exclusion).
92
-
- And the opening square bracket `pattern:'['`.
120
+
- Symbols `pattern:. + ( )` never need escaping.
121
+
- A hyphen `pattern:-` is not escaped in the beginning or the end (where it does not define a range).
122
+
- A caret `pattern:^` is only escaped in the beginning (where it means exclusion).
123
+
- The closing square bracket `pattern:]` is always escaped (if we need to look for that symbol).
93
124
94
-
In other words, all special characters are allowed except where they mean something for square brackets.
125
+
In other words, all special characters are allowed without escaping, except when they mean something for square brackets.
95
126
96
-
A dot `"."` inside square brackets means just a dot. The pattern `pattern:[.,]` would look for one of characters: either a dot or a comma.
127
+
A dot `.` inside square brackets means just a dot. The pattern `pattern:[.,]` would look for one of characters: either a dot or a comma.
97
128
98
129
In the example below the regexp `pattern:[-().^+]` looks for one of the characters `-().^+`:
The reason is that without flag `pattern:u` surrogate pairs are perceived as two characters, so `[𝒳-𝒴]` is interpreted as `[<55349><56499>-<55349><56500>]` (every surrogate pair is replaced with its codes). Now it's easy to see that the range `56499-55349` is invalid: its starting code `56499` is greater than the end `55349`. That's the formal reason for the error.
191
+
192
+
With the flag `pattern:u` the pattern works correctly:
Copy file name to clipboardExpand all lines: 9-regular-expressions/09-regexp-quantifiers/article.md
+33-31Lines changed: 33 additions & 31 deletions
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
Let's say we have a string like `+7(903)-123-45-67` and want to find all numbers in it. But unlike before, we are interested not in single digits, but full numbers: `7, 903, 123, 45, 67`.
4
4
5
-
A number is a sequence of 1 or more digits `pattern:\d`. To mark how many we need, we need to append a *quantifier*.
5
+
A number is a sequence of 1 or more digits `pattern:\d`. To mark how many we need, we can append a *quantifier*.
6
6
7
7
## Quantity {n}
8
8
@@ -12,7 +12,7 @@ A quantifier is appended to a character (or a character class, or a `[...]` set
12
12
13
13
It has a few advanced forms, let's see examples:
14
14
15
-
The exact count: `{5}`
15
+
The exact count: `pattern:{5}`
16
16
: `pattern:\d{5}` denotes exactly 5 digits, the same as `pattern:\d\d\d\d\d`.
17
17
18
18
The example below looks for a 5-digit number:
@@ -23,7 +23,7 @@ The exact count: `{5}`
23
23
24
24
We can add `\b` to exclude longer numbers: `pattern:\b\d{5}\b`.
25
25
26
-
The range: `{3,5}`, match 3-5 times
26
+
The range: `pattern:{3,5}`, match 3-5 times
27
27
: To find numbers from 3 to 5 digits we can put the limits into curly braces: `pattern:\d{3,5}`
We added an optional slash `pattern:/?` near the beginning of the pattern. Had to escape it with a backslash, otherwise JavaScript would think it is the pattern end.
```smart header="To make a regexp more precise, we often need make it more complex"
131
135
We can see one common rule in these examples: the more precise is the regular expression -- the longer and more complex it is.
132
136
133
-
For instance, for HTML tags we could use a simpler regexp: `pattern:<\w+>`.
134
-
135
-
...But because `pattern:\w` means any Latin letter or a digit or `'_'`, the regexp also matches non-tags, for instance `match:<_>`. So it's much simpler than `pattern:<[a-z][a-z0-9]*>`, but less reliable.
137
+
For instance, for HTML tags we could use a simpler regexp: `pattern:<\w+>`. But as HTML has stricter restrictions for a tag name, `pattern:<[a-z][a-z0-9]*>` is more reliable.
136
138
137
-
Are we ok with `pattern:<\w+>` or we need `pattern:<[a-z][a-z0-9]*>`?
139
+
Can we use`pattern:<\w+>` or we need `pattern:<[a-z][a-z0-9]*>`?
138
140
139
-
In real life both variants are acceptable. Depends on how tolerant we can be to "extra" matches and whether it's difficult or not to filter them out by other means.
141
+
In real life both variants are acceptable. Depends on how tolerant we can be to "extra" matches and whether it's difficult or not to remove them from the result by other means.
Copy file name to clipboardExpand all lines: 9-regular-expressions/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md
+3-5Lines changed: 3 additions & 5 deletions
Original file line number
Diff line number
Diff line change
@@ -1,13 +1,11 @@
1
1
We need to find the beginning of the comment `match:<!--`, then everything till the end of `match:-->`.
2
2
3
-
The first idea could be `pattern:<!--.*?-->` -- the lazy quantifier makes the dot stop right before `match:-->`.
3
+
An acceptable variant is `pattern:<!--.*?-->` -- the lazy quantifier makes the dot stop right before `match:-->`. We also need to add flag `pattern:s` for the dot to include newlines.
4
4
5
-
But a dot in JavaScript means "any symbol except the newline". So multiline comments won't be found.
6
-
7
-
We can use `pattern:[\s\S]` instead of the dot to match "anything":
Copy file name to clipboardExpand all lines: 9-regular-expressions/10-regexp-greedy-and-lazy/article.md
+21-24Lines changed: 21 additions & 24 deletions
Original file line number
Diff line number
Diff line change
@@ -8,7 +8,7 @@ Let's take the following task as an example.
8
8
9
9
We have a text and need to replace all quotes `"..."` with guillemet marks: `«...»`. They are preferred for typography in many countries.
10
10
11
-
For instance: `"Hello, world"` should become `«Hello, world»`. Some countries prefer other quotes, like`„Witam, świat!”` (Polish) or `「你好,世界」` (Chinese), but for our task let's choose `«...»`.
11
+
For instance: `"Hello, world"` should become `«Hello, world»`. There exist other quotes, such as`„Witam, świat!”` (Polish) or `「你好,世界」` (Chinese), but for our task let's choose `«...»`.
12
12
13
13
The first thing to do is to locate quoted strings, and then we can replace them.
14
14
@@ -35,7 +35,7 @@ That can be described as "greediness is the cause of all evil".
35
35
To find a match, the regular expression engine uses the following algorithm:
36
36
37
37
- For every position in the string
38
-
-Match the pattern at that position.
38
+
-Try to match the pattern at that position.
39
39
- If there's no match, go to the next position.
40
40
41
41
These common words do not make it obvious why the regexp fails, so let's elaborate how the search works for the pattern `pattern:".+"`.
@@ -44,7 +44,7 @@ These common words do not make it obvious why the regexp fails, so let's elabora
44
44
45
45
The regular expression engine tries to find it at the zero position of the source string `subject:a "witch" and her "broom" is one`, but there's `subject:a` there, so there's immediately no match.
46
46
47
-
Then it advances: goes to the next positions in the source string and tries to find the first character of the pattern there, and finally finds the quote at the 3rd position:
47
+
Then it advances: goes to the next positions in the source string and tries to find the first character of the pattern there, fails again, and finally finds the quote at the 3rd position:
48
48
49
49

50
50
@@ -54,23 +54,23 @@ These common words do not make it obvious why the regexp fails, so let's elabora
54
54
55
55

56
56
57
-
3. Then the dot repeats because of the quantifier `pattern:.+`. The regular expression engine builds the match by taking characters one by one while it is possible.
57
+
3. Then the dot repeats because of the quantifier `pattern:.+`. The regular expression engine adds to the match one character after another.
58
58
59
-
...When does it become impossible? All characters match the dot, so it only stops when it reaches the end of the string:
59
+
...Until when? All characters match the dot, so it only stops when it reaches the end of the string:
60
60
61
61

62
62
63
-
4. Now the engine finished repeating for `pattern:.+` and tries to find the next character of the pattern. It's the quote `pattern:"`. But there's a problem: the string has finished, there are no more characters!
63
+
4. Now the engine finished repeating `pattern:.+` and tries to find the next character of the pattern. It's the quote `pattern:"`. But there's a problem: the string has finished, there are no more characters!
64
64
65
65
The regular expression engine understands that it took too many `pattern:.+` and starts to *backtrack*.
66
66
67
67
In other words, it shortens the match for the quantifier by one character:
68
68
69
69

70
70
71
-
Now it assumes that `pattern:.+` ends one character before the end and tries to match the rest of the pattern from that position.
71
+
Now it assumes that `pattern:.+` ends one character before the string end and tries to match the rest of the pattern from that position.
72
72
73
-
If there were a quote there, then that would be the end, but the last character is `subject:'e'`, so there's no match.
73
+
If there were a quote there, then the search would end, but the last character is `subject:'e'`, so there's no match.
74
74
75
75
5. ...So the engine decreases the number of repetitions of `pattern:.+` by one more character:
76
76
@@ -84,19 +84,19 @@ These common words do not make it obvious why the regexp fails, so let's elabora
84
84
85
85
7. The match is complete.
86
86
87
-
8. So the first match is `match:"witch" and her "broom"`. The further search starts where the first match ends, but there are no more quotes in the rest of the string `subject:is one`, so no more results.
87
+
8. So the first match is `match:"witch" and her "broom"`. If the regular expression has flag `pattern:g`, then the search will continue from where the first match ends. There are no more quotes in the rest of the string `subject:is one`, so no more results.
88
88
89
89
That's probably not what we expected, but that's how it works.
90
90
91
-
**In the greedy mode (by default) the quantifier is repeated as many times as possible.**
91
+
**In the greedy mode (by default) a quantifier is repeated as many times as possible.**
92
92
93
-
The regexp engine tries to fetch as many characters as it can by`pattern:.+`, and then shortens that one by one.
93
+
The regexp engine adds to the match as many characters as it can for`pattern:.+`, and then shortens that one by one, if the rest of the pattern doesn't match.
94
94
95
-
For our task we want another thing. That's what the lazy quantifier mode is for.
95
+
For our task we want another thing. That's where a lazy mode can help.
96
96
97
97
## Lazy mode
98
98
99
-
The lazy mode of quantifier is an opposite to the greedy mode. It means: "repeat minimal number of times".
99
+
The lazy mode of quantifiers is an opposite to the greedy mode. It means: "repeat minimal number of times".
100
100
101
101
We can enable it by putting a question mark `pattern:'?'` after the quantifier, so that it becomes `pattern:*?` or `pattern:+?` or even `pattern:??` for `pattern:'?'`.
102
102
@@ -149,20 +149,19 @@ Other quantifiers remain greedy.
149
149
For instance:
150
150
151
151
```js run
152
-
alert( "123 456".match(/\d+\d+?/g) ); // 123 4
152
+
alert( "123 456".match(/\d+\d+?/) ); // 123 4
153
153
```
154
154
155
-
1. The pattern `pattern:\d+` tries to match as many numbers as it can (greedy mode), so it finds `match:123` and stops, because the next character is a space `pattern:' '`.
156
-
2. Then there's a space in pattern, it matches.
155
+
1. The pattern `pattern:\d+` tries to match as many digits as it can (greedy mode), so it finds `match:123` and stops, because the next character is a space `pattern:' '`.
156
+
2. Then there's a space in the pattern, it matches.
157
157
3. Then there's `pattern:\d+?`. The quantifier is in lazy mode, so it finds one digit `match:4` and tries to check if the rest of the pattern matches from there.
158
158
159
159
...But there's nothing in the pattern after `pattern:\d+?`.
160
160
161
161
The lazy mode doesn't repeat anything without a need. The pattern finished, so we're done. We have a match `match:123 4`.
162
-
4. The next search starts from the character `5`.
163
162
164
163
```smart header="Optimizations"
165
-
Modern regular expression engines can optimize internal algorithms to work faster. So they may work a bit different from the described algorithm.
164
+
Modern regular expression engines can optimize internal algorithms to work faster. So they may work a bit differently from the described algorithm.
166
165
167
166
But to understand how regular expressions work and to build regular expressions, we don't need to know about that. They are only used internally to optimize things.
168
167
@@ -264,7 +263,7 @@ That's what's going on:
264
263
2. Then it looks for `pattern:.*?`: takes one character (lazily!), check if there's a match for `pattern:" class="doc">` (none).
265
264
3. Then takes another character into `pattern:.*?`, and so on... until it finally reaches `match:" class="doc">`.
266
265
267
-
But the problem is: that's already beyond the link, in another tag `<p>`. Not what we want.
266
+
But the problem is: that's already beyond the link`<a...>`, in another tag `<p>`. Not what we want.
268
267
269
268
Here's the picture of the match aligned with the text:
270
269
@@ -273,11 +272,9 @@ Here's the picture of the match aligned with the text:
So, we need the pattern to look for `<a href="...something..." class="doc">`, but both greedy and lazy variants have problems.
277
276
278
-
We need the pattern to look for `<a href="...something..." class="doc">`, but both greedy and lazy variants have problems.
279
-
280
-
The correct variant would be: `pattern:href="[^"]*"`. It will take all characters inside the `href` attribute till the nearest quote, just what we need.
277
+
The correct variant can be: `pattern:href="[^"]*"`. It will take all characters inside the `href` attribute till the nearest quote, just what we need.
281
278
282
279
A working example:
283
280
@@ -301,4 +298,4 @@ Greedy
301
298
Lazy
302
299
: Enabled by the question mark `pattern:?` after the quantifier. The regexp engine tries to match the rest of the pattern before each repetition of the quantifier.
303
300
304
-
As we've seen, the lazy mode is not a "panacea" from the greedy search. An alternative is a "fine-tuned" greedy search, with exclusions. Soon we'll see more examples of it.
301
+
As we've seen, the lazy mode is not a "panacea" from the greedy search. An alternative is a "fine-tuned" greedy search, with exclusions, as in the pattern `pattern:"[^"]+"`.
Copy file name to clipboardExpand all lines: 9-regular-expressions/11-regexp-groups/5-parse-expression/solution.md
+11-8Lines changed: 11 additions & 8 deletions
Original file line number
Diff line number
Diff line change
@@ -1,16 +1,19 @@
1
1
A regexp for a number is: `pattern:-?\d+(\.\d+)?`. We created it in previous tasks.
2
2
3
-
An operator is `pattern:[-+*/]`.
3
+
An operator is `pattern:[-+*/]`. The hyphen `pattern:-` goes first in the square brackets, because in the middle it would mean a character range, while we just want a character `-`.
4
4
5
-
Please note:
6
-
- Here the dash `pattern:-` goes first in the brackets, because in the middle it would mean a character range, while we just want a character `-`.
7
-
- A slash `/` should be escaped inside a JavaScript regexp `pattern:/.../`, we'll do that later.
5
+
The slash `/` should be escaped inside a JavaScript regexp `pattern:/.../`, we'll do that later.
8
6
9
7
We need a number, an operator, and then another number. And optional spaces between them.
10
8
11
9
The full regular expression: `pattern:-?\d+(\.\d+)?\s*[-+*/]\s*-?\d+(\.\d+)?`.
12
10
13
-
To get a result as an array let's put parentheses around the data that we need: numbers and the operator: `pattern:(-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?)`.
11
+
It has 3 parts, with `pattern:\s*` between them:
12
+
1.`pattern:-?\d+(\.\d+)?` - the first number,
13
+
1.`pattern:[-+*/]` - the operator,
14
+
1.`pattern:-?\d+(\.\d+)?` - the second number.
15
+
16
+
To make each of these parts a separate element of the result array, let's enclose them in parentheses: `pattern:(-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?)`.
14
17
15
18
In action:
16
19
@@ -29,11 +32,11 @@ The result includes:
29
32
-`result[4] == "12"` (forth group `(-?\d+(\.\d+)?)` -- the second number)
30
33
-`result[5] == undefined` (fifth group `(\.\d+)?` -- the last decimal part is absent, so it's undefined)
31
34
32
-
We only want the numbers and the operator, without the full match or the decimal parts.
35
+
We only want the numbers and the operator, without the full match or the decimal parts, so let's "clean" the result a bit.
33
36
34
-
The full match (the arrays first item) can be removed by shifting the array `pattern:result.shift()`.
37
+
The full match (the arrays first item) can be removed by shifting the array `result.shift()`.
35
38
36
-
The decimal groups can be removed by making them into non-capturing groups, by adding `pattern:?:` to the beginning: `pattern:(?:\.\d+)?`.
39
+
Groups that contain decimal parts (number 2 and 4) `pattern:(.\d+)` can be excluded by adding`pattern:?:` to the beginning: `pattern:(?:\.\d+)?`.
0 commit comments