ok
|
@ -0,0 +1,132 @@
|
|||
# Patterns and flags
|
||||
|
||||
Regular expressions is a powerful way of searching and replacing inside a string.
|
||||
|
||||
In Javascript regular expressions are implemented using objects of a built-in `RegExp` class and integrated with strings.
|
||||
|
||||
Please note that regular expressions vary between programming languages. In this tutorial we concentrate on Javascript. Of course there's a lot in common, but they are a somewhat different in Perl, Ruby, PHP etc.
|
||||
|
||||
[cut]
|
||||
|
||||
## Regular expressions
|
||||
|
||||
A regular expression (also "regexp", or just "reg") consists of a *pattern* and optional *flags*.
|
||||
|
||||
There are two syntaxes to create a regular expression object.
|
||||
|
||||
The long syntax:
|
||||
|
||||
```js
|
||||
regexp = new RegExp("pattern", "flags");
|
||||
```
|
||||
|
||||
...And the short one, using slashes `"/"`:
|
||||
|
||||
```js
|
||||
regexp = /pattern/; // no flags флагов
|
||||
regexp = /pattern/gmi; // with flags g,m and i (to be covered soon)
|
||||
```
|
||||
|
||||
Slashes `"/"` tell Javascript that we are creating a regular expression. They play the same role as quotes for strings.
|
||||
|
||||
## Usage
|
||||
|
||||
To search inside a string, we can use method [search](mdn:js/String/search).
|
||||
|
||||
Here's an example:
|
||||
|
||||
```js run
|
||||
let str = "I love Javascript!"; // will search here
|
||||
|
||||
let regexp = /love/;
|
||||
alert( str.search(regexp) ); // 2
|
||||
```
|
||||
|
||||
The `str.search` method looks for the pattern `pattern:/love/` and returns the position inside the string. As we might guess, `pattern:/love/` is the simplest possible pattern. What it does is a simple substring search.
|
||||
|
||||
The code above is the same as:
|
||||
|
||||
```js run
|
||||
let str = "I love Javascript!"; // will search here
|
||||
|
||||
let substr = 'love';
|
||||
alert( str.search(substr) ); // 2
|
||||
```
|
||||
|
||||
So searching for `pattern:/love/` is the same as searching for `"love"`.
|
||||
|
||||
But that's only for now. Soon we'll create more complex regular expressions with much searching more power.
|
||||
|
||||
```smart header="Colors"
|
||||
From here on the color scheme is:
|
||||
|
||||
- regexp -- `pattern:red`
|
||||
- string (where we search) -- `subject:blue`
|
||||
- result -- `match:green`
|
||||
```
|
||||
|
||||
|
||||
````smart header="When to use `new RegExp`?"
|
||||
Normally we use the short syntax `/.../`. But it does not allow any variables insertions, so we must know the exact regexp at the time of writing the code.
|
||||
|
||||
From the other hand, `new RegExp` allows to construct a pattern dynamically from a string.
|
||||
|
||||
So we can figure out what we need to search and create `new RegExp` from it:
|
||||
|
||||
```js run
|
||||
let search = prompt("What you want to search?", "love");
|
||||
let regexp = new RegExp(search);
|
||||
|
||||
// find whatever the user wants
|
||||
alert( "I love Javascript".search(regexp));
|
||||
```
|
||||
````
|
||||
|
||||
|
||||
## Flags
|
||||
|
||||
Regular expressions may have flags that affect the search.
|
||||
|
||||
There are only 5 of them in Javascript:
|
||||
|
||||
`i`
|
||||
: With this flag the search is case-insensitive: no difference between `А` and `а` (see the example below).
|
||||
|
||||
`g`
|
||||
: With this flag the search looks for all matches, without it -- only the first one (we'll see uses in the next chapter).
|
||||
|
||||
`m`
|
||||
: Multiline mode (will cover in [todo]).
|
||||
|
||||
`u`
|
||||
: Enables full unicode support. The flag enables correct processing of surrogate pairs. More about that in the chapter <info:regexp-unicode>.
|
||||
|
||||
`y`
|
||||
: Sticky mode (covered in [todo])
|
||||
|
||||
|
||||
|
||||
## The "i" flag
|
||||
|
||||
The simplest flag is `i`.
|
||||
|
||||
An example with it:
|
||||
|
||||
```js run
|
||||
let str = "I love Javascript!";
|
||||
|
||||
alert( str.search(/LOVE/) ); // -1 (not found)
|
||||
alert( str.search(/LOVE/i) ); // 2
|
||||
```
|
||||
|
||||
1. The first search returns `-1` (not found), because the search is case-sensitive by default.
|
||||
2. With the flag `pattern:/LOVE/i` the search found `match:love` at position 2.
|
||||
|
||||
So the `i` flag already makes regular expressions more powerful than a simple substring search. But there's so much more. We'll cover other flags and features in the next chapters.
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
- A regular expression consists of a pattern and optional flags: `g`, `i`, `m`, `u`, `y`.
|
||||
- Without flags and special symbols that we'll study later, the search by a regexp is the same as a substring search.
|
||||
- The method `str.search(regexp)` returns the index where the match is found or `-1` if there's no match.
|
370
10-regular-expressions-javascript/02-regexp-methods/article.md
Normal file
|
@ -0,0 +1,370 @@
|
|||
# Methods of RegExp and String
|
||||
|
||||
There are two sets of methods to deal with regular expressions.
|
||||
|
||||
1. First, regular expressions are objects of the built-in [RegExp](mdn:js/RegExp) class, it provides many methods.
|
||||
2. Besides that, there are methods in regular strings can work with regexps.
|
||||
|
||||
The structure is a bit messed up, so we'll first consider methods separately, and then -- practical recipes for common tasks.
|
||||
|
||||
[cut]
|
||||
|
||||
## str.search(reg)
|
||||
|
||||
We've seen this method already. It returns the position of the first match or `-1` if none found:
|
||||
|
||||
```js run
|
||||
let str = "A drop of ink may make a million think";
|
||||
|
||||
alert( str.search( *!*/a/i*/!* ) ); // 0 (the first position)
|
||||
```
|
||||
|
||||
**The important limitation: `search` always looks for the first match.**
|
||||
|
||||
We can't find next positions using `search`, there's just no syntax for that. But there are other mathods that can.
|
||||
|
||||
## str.match(reg), no "g" flag
|
||||
|
||||
The method `str.match` behavior varies depending on the `g` flag. First let's see the case without it.
|
||||
|
||||
Then `str.match(reg)` looks for the first match only.
|
||||
|
||||
The result is an array with that match and additional properties:
|
||||
|
||||
- `index` -- the position of the match inside the string,
|
||||
- `input` -- the subject string.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
let str = "Fame is the thirst of youth";
|
||||
|
||||
let result = str.match( *!*/fame/i*/!* );
|
||||
|
||||
alert( result[0] ); // Fame (the match)
|
||||
alert( result.index ); // 0 (at the zero position)
|
||||
alert( result.input ); // "Fame is the thirst of youth" (the string)
|
||||
```
|
||||
|
||||
The array may have more than one element.
|
||||
|
||||
**If a part of the pattern is delimited by brackets `(...)`, then it becomes a separate element of the array.**
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
lar str = "Javascript is a programming language";
|
||||
|
||||
let result = str.match( *!*/JAVA(SCRIPT)/i*/!* );
|
||||
|
||||
alert( result[0] ); // Javascript (the whole match)
|
||||
alert( result[1] ); // script (the part of the match that corresponds to the brackets)
|
||||
alert( result.index ); // 0
|
||||
alert( result.input ); // Javascript is a programming language
|
||||
```
|
||||
|
||||
Due to the `i` flag the search is case-insensitive, so it finds `match:Javascript`. The part of the match that corresponds to `pattern:SCRIPT` becomes a separate array item.
|
||||
|
||||
We'll be back to brackets later in the chapter [todo]. They are great for search-and-replace.
|
||||
|
||||
## str.match(reg) with "g" flag
|
||||
|
||||
When there's a `"g"` flag, then `str.match` returns an array of all matches. There are no additional properties in that array, and brackets do not create any elements.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
let str = "HO-Ho-ho!";
|
||||
|
||||
let result = str.match( *!*/ho/ig*/!* );
|
||||
|
||||
alert( result ); // HO, Ho, ho (all matches, case-insensitive)
|
||||
```
|
||||
|
||||
With brackets nothing changes, here we go:
|
||||
|
||||
|
||||
|
||||
```js run
|
||||
let str = "HO-Ho-ho!";
|
||||
|
||||
let result = str.match( *!*/h(o)/ig*/!* );
|
||||
|
||||
alert( result ); // HO, Ho, ho
|
||||
```
|
||||
|
||||
So, with `g` flag the `result` is a simple array of matches. No additional properties.
|
||||
|
||||
If we want to get information about match positions and use brackets then we should use [RegExp#exec](mdn:js/RegExp/exec) method that we'll cover below.
|
||||
|
||||
````warn header="If there are no matches, the call to `match` returns `null`"
|
||||
Please note, that's important. If there were no matches, the result is not an empty array, but `null`.
|
||||
|
||||
Keep that in mind to evade pitfalls like this:
|
||||
|
||||
```js run
|
||||
let str = "Hey-hey-hey!";
|
||||
|
||||
alert( str.match(/ho/gi).length ); // error! there's no length of null
|
||||
```
|
||||
````
|
||||
|
||||
## str.split(regexp|substr, limit)
|
||||
|
||||
Splits the string using the regexp (or a substring) as a delimiter.
|
||||
|
||||
We already used `split` with strings, like this:
|
||||
|
||||
```js run
|
||||
alert('12-34-56'.split('-')) // [12, 34, 56]
|
||||
```
|
||||
|
||||
But we can also pass a regular expression:
|
||||
|
||||
```js run
|
||||
alert('12-34-56'.split(/-/)) // [12, 34, 56]
|
||||
```
|
||||
|
||||
## str.replace(str|reg, str|func)
|
||||
|
||||
The swiss army knife for search and replace in strings.
|
||||
|
||||
The simplest use -- search and replace a substring, like this:
|
||||
|
||||
```js run
|
||||
// replace a dash by a colon
|
||||
alert('12-34-56'.replace("-", ":")) // 12:34-56
|
||||
```
|
||||
|
||||
When the first argument of `replace` is a string, it only looks for the first match.
|
||||
|
||||
To find all dashes, we need to use not the string `"-"`, but a regexp `pattern:/-/g`, with an obligatory `g` flag:
|
||||
|
||||
```js run
|
||||
// replace all dashes by a colon
|
||||
alert( '12-34-56'.replace( *!*/-/g*/!*, ":" ) ) // 12:34:56
|
||||
```
|
||||
|
||||
The second argument is a replacement string.
|
||||
|
||||
We can use special characters in it:
|
||||
|
||||
| Symbol | Inserts |
|
||||
|--------|--------|
|
||||
|`$$`|`"$"` |
|
||||
|`$&`|the whole match|
|
||||
|<code>$`</code>|a part of the string before the match|
|
||||
|`$'`|a part of the string after the match|
|
||||
|`$n`|if `n` is a 1-2 digit number, then it means the contents of n-th brackets counting fro left to right|
|
||||
|
||||
For instance let's use `$&` to replace all entries of `"John"` by `"Mr.John"`:
|
||||
|
||||
```js run
|
||||
let str = "John Doe, John Smith and John Bull.";
|
||||
|
||||
// for each John - replace it with Mr. and then John
|
||||
alert(str.replace(/John/g, 'Mr.$&'));
|
||||
// "Mr.John Doe, Mr.John Smith and Mr.John Bull.";
|
||||
```
|
||||
|
||||
Brackets are very often used together with `$1`, `$2`, like this:
|
||||
|
||||
```js run
|
||||
let str = "John Smith";
|
||||
|
||||
alert(str.replace(/(John) (Smith)/, '$2, $1')) // Smith, John
|
||||
```
|
||||
|
||||
**For situations that require "smart" replacements, the second argument can be a function.**
|
||||
|
||||
It will be called for each match, and its result will be inserted as a replacement.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
let i = 0;
|
||||
|
||||
// replace each "ho" by the result of the function
|
||||
alert("HO-Ho-ho".replace(/ho/gi, function() {
|
||||
return ++i;
|
||||
})); // 1-2-3
|
||||
```
|
||||
|
||||
In the example above the function just returns the next number every time, but usually the result is based on the match.
|
||||
|
||||
The function is called with arguments `func(str, p1, p2, ..., pn, offset, s)`:
|
||||
|
||||
1. `str` -- the match,
|
||||
2. `p1, p2, ..., pn` -- contents of brackets (if there are any),
|
||||
3. `offset` -- position of the match,
|
||||
4. `s` -- the source string.
|
||||
|
||||
If there are no brackets in the regexp, then the function always has 3 arguments: `func(str, offset, s)`.
|
||||
|
||||
Let's use it to show full information about matches:
|
||||
|
||||
```js run
|
||||
// show and replace all matches
|
||||
function replacer(str, offset, s) {
|
||||
alert(`Found ${str} at position ${offset} in string ${s}`);
|
||||
return str.toLowerCase();
|
||||
}
|
||||
|
||||
let result = "HO-Ho-ho".replace(/ho/gi, replacer);
|
||||
alert( 'Result: ' + result ); // Result: ho-ho-ho
|
||||
|
||||
// shows each match:
|
||||
// Found HO at position 0 in string HO-Ho-ho
|
||||
// Found Ho at position 3 in string HO-Ho-ho
|
||||
// Found ho at position 6 in string HO-Ho-ho
|
||||
```
|
||||
|
||||
In the example below there are two brackets, so `replacer` is called with 5 arguments: `str` is the full match, then brackets, and then `offset` and `s`:
|
||||
|
||||
```js run
|
||||
function replacer(str, name, surname, offset, s) {
|
||||
// name is the first bracket, surname is the second one
|
||||
return surname + ", " + name;
|
||||
}
|
||||
|
||||
let str = "John Smith";
|
||||
|
||||
alert(str.replace(/(John) (Smith)/, replacer)) // Smith, John
|
||||
```
|
||||
|
||||
Using a function gives us the ultimate replacement power, because it gets all the information about the match, has access to outer variables and can do everything.
|
||||
|
||||
## regexp.test(str)
|
||||
|
||||
Let's move on to the methods of `RegExp` class, that are callable on regexps themselves.
|
||||
|
||||
The `test` method looks for any match and returns `true/false` whether he found it.
|
||||
|
||||
So it's basically the same as `str.search(reg) != -1`, for instance:
|
||||
|
||||
```js run
|
||||
let str = "I love Javascript";
|
||||
|
||||
// these two tests do the same
|
||||
alert( *!*/love/i*/!*.test(str) ); // true
|
||||
alert( str.search(*!*/love/i*/!*) != -1 ); // true
|
||||
```
|
||||
|
||||
An example with the negative answer:
|
||||
|
||||
```js run
|
||||
let str = "Bla-bla-bla";
|
||||
|
||||
alert( *!*/love/i*/!*.test(str) ); // false
|
||||
alert( str.search(*!*/love/i*/!*) != -1 ); // false
|
||||
```
|
||||
|
||||
## regexp.exec(str)
|
||||
|
||||
We've already seen these searching methods:
|
||||
|
||||
- `search` -- looks for the position of the match,
|
||||
- `match` -- if there's no `g` flag, returns the first match with brackets,
|
||||
- `match` -- if there's a `g` flag -- returns all matches, without separating brackets.
|
||||
|
||||
The `regexp.exec` method is a bit harder to use, but it allows to search all matches with brackets and positions.
|
||||
|
||||
It behaves differently depending on whether the regexp has the `g` flag.
|
||||
|
||||
- If there's no `g`, then `regexp.exec(str)` returns the first match, exactly as `str.match(reg)`.
|
||||
- If there's `g`, then `regexp.exec(str)` returns the first match and *remembers* the position after it in `regexp.lastIndex` property. The next call starts to search from `regexp.lastIndex` and returns the next match. If there are no more matches then `regexp.exec` returns `null` and `regexp.lastIndex` is set to `0`.
|
||||
|
||||
As we can see, the method gives us nothing new if we use it without the `g` flag, because `str.match` does exactly the same.
|
||||
|
||||
But the `g` flag allows to get all matches with their positions and bracket groups.
|
||||
|
||||
Here's the example how subsequent `regexp.exec` calls return matches one by one:
|
||||
|
||||
```js run
|
||||
let str = "A lot about Javascript at https://javascript.info";
|
||||
|
||||
let regexp = /JAVA(SCRIPT)/ig;
|
||||
|
||||
*!*
|
||||
// Look for the first match
|
||||
*/!*
|
||||
let matchOne = regexp.exec(str);
|
||||
alert( matchOne[0] ); // Javascript
|
||||
alert( matchOne[1] ); // script
|
||||
alert( matchOne.index ); // 12 (the position of the match)
|
||||
alert( matchOne.input ); // the same as str
|
||||
|
||||
alert( regexp.lastIndex ); // 22 (the position after the match)
|
||||
|
||||
*!*
|
||||
// Look for the second match
|
||||
*/!*
|
||||
let matchTwo = regexp.exec(str); // continue searching from regexp.lastIndex
|
||||
alert( matchTwo[0] ); // javascript
|
||||
alert( matchTwo[1] ); // script
|
||||
alert( matchTwo.index ); // 34 (the position of the match)
|
||||
alert( matchTwo.input ); // the same as str
|
||||
|
||||
alert( regexp.lastIndex ); // 44 (the position after the match)
|
||||
|
||||
*!*
|
||||
// Look for the third match
|
||||
*/!*
|
||||
let matchThree = regexp.exec(str); // continue searching from regexp.lastIndex
|
||||
alert( matchThree ); // null (no match)
|
||||
|
||||
alert( regexp.lastIndex ); // 0 (reset)
|
||||
```
|
||||
|
||||
As we can see, each `regexp.exec` call returns the match in a "full format": as an array with brackets, `index` and `input` properties.
|
||||
|
||||
The main use case for `regexp.exec` is to find all matches in a loop:
|
||||
|
||||
```js run
|
||||
let str = 'A lot about Javascript at https://javascript.info';
|
||||
|
||||
let regexp = /javascript/ig;
|
||||
|
||||
let result;
|
||||
|
||||
while (result = regexp.exec(str)) {
|
||||
alert( `Found ${result[0]} at ${result.index}` );
|
||||
}
|
||||
```
|
||||
|
||||
The loop continues until `regexp.exec` returns `null` that means "no more matches".
|
||||
|
||||
````smart header="Search from the given position"
|
||||
We can force `regexp.exec` to start searching from the given position by setting `lastIndex` manually:
|
||||
|
||||
```js run
|
||||
let str = 'A lot about Javascript at https://javascript.info';
|
||||
|
||||
let regexp = /javascript/ig;
|
||||
regexp.lastIndex = 30;
|
||||
|
||||
alert( regexp.exec(str).index ); // 34, the search starts from the 30th position
|
||||
```
|
||||
````
|
||||
|
||||
## Summary, recipes
|
||||
|
||||
Methods become much easier to understand if we separate them by their use in real-life tasks.
|
||||
|
||||
To search for the first match only:
|
||||
: - Find the position of the first match -- `str.search(reg)`.
|
||||
- Find the full match -- `str.match(reg)`.
|
||||
- Check if there's a match -- `regexp.test(str)`.
|
||||
- Find the match from the given position -- `regexp.exec(str)`, set `regexp.lastIndex` to position.
|
||||
|
||||
To search for all matches:
|
||||
: - An array of matches -- `str.match(reg)`, the regexp with `g` flag.
|
||||
- Get all matches with full information about each one -- `regexp.exec(str)` with `g` flag in the loop.
|
||||
|
||||
To search and replace:
|
||||
: - Replace with another string or a function result -- `str.replace(reg, str|func)`
|
||||
|
||||
To split the string:
|
||||
: - `str.split(str|reg)`
|
||||
|
||||
Now we know the methods and can use regular expressions. But we need to learn their syntax and capabilities, so let's move on.
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
The answer: `pattern:\b\d\d:\d\d\b`.
|
||||
|
||||
```js run
|
||||
alert( "Breakfast at 09:00 in the room 123:456.".match( /\b\d\d:\d\d\b/ ) ); // 09:00
|
||||
```
|
|
@ -0,0 +1,8 @@
|
|||
# Find the time
|
||||
|
||||
The time has a format: `hours:minutes`. Both hours and minutes has two digits, like `09:00`.
|
||||
|
||||
Make a regexp to find time in the string: `subject:Breakfast at 09:00 in the room 123:456.`
|
||||
|
||||
P.S. In this task there's no need to check time correctness yet, so `25:99` can also be a valid result.
|
||||
P.P.S. The regexp shouldn't match `123:456`.
|
|
@ -0,0 +1,223 @@
|
|||
# Character classes
|
||||
|
||||
Consider a practical task -- we have a phone number `"+7(903)-123-45-67"`, and we need to find all digits in that string. Other characters do not interest us.
|
||||
|
||||
A character class is a special notation that matches any symbol from the set.
|
||||
|
||||
[cut]
|
||||
|
||||
For instance, there's a "digit" class. It's written as `\d`. We put it in the pattern, and during the search any digit matches it.
|
||||
|
||||
That is: a regexp `pattern:/\d/` looks for a digit:
|
||||
|
||||
```js run
|
||||
let str = "+7(903)-123-45-67";
|
||||
|
||||
let reg = /\d/;
|
||||
|
||||
alert( str.match(reg) ); // 7
|
||||
```
|
||||
|
||||
The regexp is not global in the example above, so it only looks for the first match.
|
||||
|
||||
Let's add the `g` flag to look for all digits:
|
||||
|
||||
```js run
|
||||
let str = "+7(903)-123-45-67";
|
||||
|
||||
let reg = /\d/g;
|
||||
|
||||
alert( str.match(reg) ); // array of matches: 7,9,0,3,1,2,3,4,5,6,7
|
||||
```
|
||||
|
||||
## Most used classes: \d \s \w
|
||||
|
||||
That was a character class for digits. There are other character classes as well.
|
||||
|
||||
Most used are:
|
||||
|
||||
`\d` ("d" is from "digit")
|
||||
: A digit: a character from `0` to `9`.
|
||||
|
||||
`\s` ("s" is from "space")
|
||||
: A space symbol: that includes spaces, tabs, newlines.
|
||||
|
||||
`\w` ("w" is from "word")
|
||||
: A "wordly" character: either a letter of English alphabet or a digit or an underscore. Non-english letters (like cyricllic or hindi) do not belong to `\w`.
|
||||
|
||||
For instance, `pattern:\d\s\w` means a digit followed by a space character followed by a wordly character, like `"1 Z"`.
|
||||
|
||||
A regexp may contain both regular symbols and character classes.
|
||||
|
||||
For instance, `pattern:CSS\d` matches a string `match:CSS` with a digit after it:
|
||||
|
||||
```js run
|
||||
let str = "CSS4 is cool";
|
||||
let reg = /CSS\d/
|
||||
|
||||
alert( str.match(reg) ); // CSS4
|
||||
```
|
||||
|
||||
Also we can use many character classes:
|
||||
|
||||
```js run
|
||||
alert( "I love HTML5!".match(/\s\w\w\w\w\d/) ); // 'HTML5'
|
||||
```
|
||||
|
||||
The match (each character class corresponds to one result character):
|
||||
|
||||

|
||||
|
||||
## Word boundary: \b
|
||||
|
||||
The word boundary `pattern:\b` -- is a special character class.
|
||||
|
||||
It does not denote a character, but rather a boundary between characters.
|
||||
|
||||
For instance, `pattern:\bJava\b` matches `match:Java` in the string `subject:Hello, Java!`, but not in the script `subject:Hello, Javascript!`.
|
||||
|
||||
```js run
|
||||
alert( "Hello, Java!".match(/\bJava\b/) ); // Java
|
||||
alert( "Hello, Javascript!".match(/\bJava\b/) ); // null
|
||||
```
|
||||
|
||||
The boundary has "zero width" in a sense that usually a character class means a character in the result (like a wordly or a digit), but not in this case.
|
||||
|
||||
The boundary is a test.
|
||||
|
||||
When regular expression engine is doing the search, it's moving along the string in an attempt to find the match. At each string position it tries to find the pattern.
|
||||
|
||||
When the pattern contains `pattern:\b`, it tests that the position in string fits one of the conditions:
|
||||
|
||||
- String start, and the first string character is `\w`.
|
||||
- String end, and the last string character is `\w`.
|
||||
- Inside the string: from one side is `\w`, from the other side -- not `\w`.
|
||||
|
||||
For instance, in the string `subject:Hello, Java!` the following positions fit `\b`:
|
||||
|
||||

|
||||
|
||||
So it matches `pattern:\bHello\b` and `pattern:\bJava\b`, but not `pattern:\bHell\b` (because there's no word boundary after `l`) and not `Java!\b` (because the exclamation sign is not a wordly character, so there's no word boundary after it).
|
||||
|
||||
```js run
|
||||
alert( "Hello, Java!".match(/\bHello\b/) ); // Hello
|
||||
alert( "Hello, Java!".match(/\Java\b/) ); // Java
|
||||
alert( "Hello, Java!".match(/\Hell\b/) ); // null
|
||||
alert( "Hello, Java!".match(/\Java!\b/) ); // null
|
||||
```
|
||||
|
||||
Once again let's note that `pattern:\b` makes the searching engine to test for the boundary, so that `pattern:Java\b` finds `match:Java` only when followed by a word boundary, but it does not add a letter to the result.
|
||||
|
||||
Usually we use `\b` to find standalone English words. So that if we want `"Java"` language then `pattern:\bJava\b` finds exactly a standalone word and ignores it when it's a part of `"Javascript"`.
|
||||
|
||||
Another example: a regexp `pattern:\b\d\d\b` looks for standalone two-digit numbers. In other words, it requires that before and after `pattern:\d\d` must be a symbol different from `\w` (or beginning/end of the string).
|
||||
|
||||
```js run
|
||||
alert( "1 23 456 78".match(/\b\d\d\b/g) ); // 23,78
|
||||
```
|
||||
|
||||
## Reverse classes
|
||||
|
||||
For every character class there exists a "reverse class", denoted with the same letter, but uppercased.
|
||||
|
||||
The "reverse" means that it matches all other characters, for instance:
|
||||
|
||||
`\D`
|
||||
: Non-digit: any character except `\d`, for instance a letter.
|
||||
|
||||
`\S`
|
||||
: Non-space: any character except `\s`, for instance a letter.
|
||||
|
||||
`\W`
|
||||
: Non-wordly character: anything but `\w`.
|
||||
|
||||
`\B`
|
||||
: Non-boundary: a test reverse to `\b`.
|
||||
|
||||
In the beginning of the chapter we saw how to get all digits from the phone `subject:+7(903)-123-45-67`. Let's get a "pure" phone number from the string:
|
||||
|
||||
```js run
|
||||
let str = "+7(903)-123-45-67";
|
||||
|
||||
alert( str.match(/\d/g).join('') ); // 79031234567
|
||||
```
|
||||
|
||||
An alternative way would be to find non-digits and remove them from the string:
|
||||
|
||||
|
||||
```js run
|
||||
let str = "+7(903)-123-45-67";
|
||||
|
||||
alert( str.replace(/\D/g, "") ); // 79031234567
|
||||
```
|
||||
|
||||
## Spaces are regular characters
|
||||
|
||||
Please note that regular expressions may include spaces. They are treated like regular characters.
|
||||
|
||||
Usually we pay little attention to spaces. For us strings `subject:1-5` and `subject:1 - 5` are nearly identical.
|
||||
|
||||
But if a regexp does not take spaces into account, it won' work.
|
||||
|
||||
Let's try to find digits separated by a dash:
|
||||
|
||||
```js run
|
||||
alert( "1 - 5".match(/\d-\d/) ); // null, no match!
|
||||
```
|
||||
|
||||
Here we fix it by adding spaces into the regexp:
|
||||
|
||||
```js run
|
||||
alert( "1 - 5".match(/\d - \d/) ); // 1 - 5, now it works
|
||||
```
|
||||
|
||||
Of course, spaces are needed only if we look for them. Extra spaces (just like any other extra characters) may prevent a match:
|
||||
|
||||
```js run
|
||||
alert( "1-5".match(/\d - \d/) ); // null, because the string 1-5 has no spaces
|
||||
```
|
||||
|
||||
In other words, in a regular expression all characters matter. Spaces too.
|
||||
|
||||
## A dot is any character
|
||||
|
||||
The dot `"."` is a special character class that matches *any character except a newline*.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
alert( "Z".match(/./) ); // Z
|
||||
```
|
||||
|
||||
Or in the middle of a regexp:
|
||||
|
||||
```js run
|
||||
let reg = /CS.4/;
|
||||
|
||||
alert( "CSS4".match(reg) ); // CSS4
|
||||
alert( "CS-4".match(reg) ); // CS-4
|
||||
alert( "CS 4".match(reg) ); // CS 4 (space is also a character)
|
||||
```
|
||||
|
||||
Please note that the dot means "any character", but not the "absense of a character". There must be a character to match it:
|
||||
|
||||
```js run
|
||||
alert( "CS4".match(/CS.4/) ); // null, no match because there's no character for the dot
|
||||
```
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
We covered character classes:
|
||||
|
||||
- `\d` -- digits.
|
||||
- `\D` -- non-digits.
|
||||
- `\s` -- space symbols, tabs, newlines.
|
||||
- `\S` -- all but `\s`.
|
||||
- `\w` -- English letters, digits, underscore `'_'`.
|
||||
- `\W` -- all but `\w`.
|
||||
- `'.'` -- any character except a newline.
|
||||
|
||||
If we want to search for a character that has a special meaning like a backslash or a dot, then we should escape it with a backslash: `pattern:\.`
|
||||
|
||||
Please note that a regexp may also contain string special characters such as a newline `\n`. There's no conflict with character classes, because other letters are used for them.
|
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 4 KiB |
After Width: | Height: | Size: 8.7 KiB |
|
@ -0,0 +1,50 @@
|
|||
|
||||
# Special characters
|
||||
|
||||
As we've seen, a backslash `"\"` is used to denote character classes. So it's a special character.
|
||||
|
||||
There are other special characters as well, that have special meaning in a regexp. They are used to do more powerful searches.
|
||||
|
||||
Here's a full list of them: `pattern:[ \ ^ $ . | ? * + ( )`.
|
||||
|
||||
Don't try to remember it -- when we deal with each of them separately, you'll know it by heart automatically.
|
||||
|
||||
## Escaping
|
||||
|
||||
To use a special character as a regular one, prepend it with a backslash.
|
||||
|
||||
That's also called "escaping a character".
|
||||
|
||||
For instance, we need to find a dot `pattern:'.'`. In a regular expression a dot means "any character except a newline", so if we really mean "a dot", let's put a backslash before it: `pattern:\.`.
|
||||
|
||||
```js run
|
||||
alert( "Chapter 5.1".match(/\d\.\d/) ); // 5.1
|
||||
```
|
||||
|
||||
Brackets are also special characters, so if we want them, we should use `pattern:\(`. The example below looks for a string `"g()"`:
|
||||
|
||||
```js run
|
||||
alert( "function g()".match(/g\(\)/) ); // "g()"
|
||||
```
|
||||
|
||||
If we're looking for a backslash `\`, then we should double it:
|
||||
|
||||
```js run
|
||||
alert( "1\2".match(/\\/) ); // '\'
|
||||
```
|
||||
|
||||
## A slash
|
||||
|
||||
The slash symbol `'/'` is not a special character, but in Javascript it is used to open and close the regexp: `pattern:/...pattern.../`, so we should escape it too.
|
||||
|
||||
Here's what a search for a slash `'/'` looks like:
|
||||
|
||||
```js run
|
||||
alert( "/".match(/\//) ); // '/'
|
||||
```
|
||||
|
||||
From the other hand, the alternative `new RegExp` syntaxes does not require escaping it:
|
||||
|
||||
```js run
|
||||
alert( "/".match(new RegExp()/\//) ); // '/'
|
||||
```
|
|
@ -0,0 +1,12 @@
|
|||
Answers: **no, yes**.
|
||||
|
||||
- In the script `subject:Java` it doesn't match anything, because `pattern:[^script]` means "any character except given ones". So the regexp looks for `"Java"` followed by one such symbol, but there's a string end, no symbols after it.
|
||||
|
||||
```js run
|
||||
alert( "Java".match(/Java[^script]/) ); // null
|
||||
```
|
||||
- Yes, because the regexp is case-insensitive, the `pattern:[^script]` part matches the character `"S"`.
|
||||
|
||||
```js run
|
||||
alert( "JavaScript".match(/Java[^script]/) ); // "JavaS"
|
||||
```
|
|
@ -0,0 +1,5 @@
|
|||
# Java[^script]
|
||||
|
||||
We have a regexp `pattern:/Java[^script]/`.
|
||||
|
||||
Does it match anything in the string `subject:Java`? In the string `subject:JavaScript`?
|
|
@ -0,0 +1,8 @@
|
|||
Ответ: `pattern:\d\d[-:]\d\d`.
|
||||
|
||||
```js run
|
||||
var re = /\d\d[-:]\d\d/g;
|
||||
alert( "Завтрак в 09:00. Обед - в 21-30".match(re) );
|
||||
```
|
||||
|
||||
Обратим внимание, что дефис `pattern:'-'` не экранирован, поскольку в начале скобок он не может иметь специального смысла.
|
|
@ -0,0 +1,11 @@
|
|||
# Найдите время в одном из форматов
|
||||
|
||||
Время может быть в формате `часы:минуты` или `часы-минуты`. И часы и минуты состоят из двух цифр, например `09:00`, `21-30`.
|
||||
|
||||
Напишите регулярное выражение для поиска времени:
|
||||
|
||||
```js
|
||||
var re = /ваше выражение/;
|
||||
alert( "Завтрак в 09:00. Обед - в 21-30".match(re) ); // 09:00, 21-30
|
||||
```
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
# Sets and ranges [...]
|
||||
|
||||
Several characters or character classes inside square brackets `[…]` mean to "search for any character among given".
|
||||
|
||||
[cut]
|
||||
|
||||
## Sets
|
||||
|
||||
For instance, `pattern:[еао]` means any of the 3 characters: `'а'`, `'е'`, or `'о'`.
|
||||
|
||||
That's calles a *set*. Sets can be used in a regexp along with regular characters:
|
||||
|
||||
```js run
|
||||
// find [t or m], and then "op"
|
||||
alert( "Mop top".match(/[tm]op/gi) ); // "Mop", "top"
|
||||
```
|
||||
|
||||
Please note that although there are multiple characters in the set, they correspond to exactly one character in the match.
|
||||
|
||||
So the example above gives no matches:
|
||||
|
||||
```js run
|
||||
// find "V", then [o or i], then "la"
|
||||
alert( "Voila".match(/V[oi]la/) ); // null, no matches
|
||||
```
|
||||
|
||||
The pattern assumes:
|
||||
|
||||
- `pattern:В`,
|
||||
- then *одну* of the letters `pattern:[oi]`,
|
||||
- then `pattern:la`.
|
||||
|
||||
So there would be a match for `match:Vola` or `match:Vila`.
|
||||
|
||||
## Ranges
|
||||
|
||||
Square brackets may also contain *character ranges*.
|
||||
|
||||
For instance, `pattern:[a-z]` is a character in range from `a` to `z`, and `pattern:[0-5]` is a digit from `0` to `5`.
|
||||
|
||||
In the example below we're searching for `"x"` followed by two digits or letters from `A` to `F`:
|
||||
|
||||
```js run
|
||||
alert( "Exception 0xAF".match(/x[0-9A-F][0-9A-F]/g) ); // xAF
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
If we want to find it too, then we can add a range `a-f`: `pattern:[0-9A-Fa-f]`. The `i` flag would allow lowercase too.
|
||||
|
||||
**Character classes are shorthands for certain character sets.**
|
||||
|
||||
For instance:
|
||||
|
||||
- **\d** -- is the same as `pattern:[0-9]`,
|
||||
- **\w** -- is the same as `pattern:[a-zA-Z0-9_]`,
|
||||
- **\s** -- is the same as `pattern:[\t\n\v\f\r ]` plus few other unicode space characters.
|
||||
|
||||
We can use character classes inside `[…]` as well.
|
||||
|
||||
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-]`.
|
||||
|
||||
We also can use a combination of classes to cover every possible character, like `pattern:[\s\S]`. That matches spaces or non-spaces -- any character. That's wider than a dot `"."`, because the dot matches any character except a newline.
|
||||
|
||||
## Excluding ranges
|
||||
|
||||
Besides normal ranges, there are "excluding" ranges that look like `pattern:[^…]`.
|
||||
|
||||
They are denoted by a caret character `^` at the start and match any character *except the given ones*.
|
||||
|
||||
For instance:
|
||||
|
||||
- `pattern:[^аеуо]` -- any character except `'a'`, `'e'`, `'y'` or `'o'`.
|
||||
- `pattern:[^0-9]` -- any character except a digit, the same as `\D`.
|
||||
- `pattern:[^\s]` -- any non-space character, same as `\S`.
|
||||
|
||||
The example below looks for any characters except letters, digits and spaces:
|
||||
|
||||
```js run
|
||||
alert( "alice15@gmail.com".match(/[^\d\sA-Z]/gi) ); // @ and .
|
||||
```
|
||||
|
||||
## No escaping in […]
|
||||
|
||||
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:\\`.
|
||||
|
||||
In square brackets the vast majority of special characters can be used without escaping:
|
||||
|
||||
- A dot `pattern:'.'`.
|
||||
- A plus `pattern:'+'`.
|
||||
- Brackets `pattern:'( )'`.
|
||||
- Dash `pattern:'-'` in the beginning or the end (where it does not define a range).
|
||||
- A caret `pattern:'^'` if not in the beginning (where it means exclusion).
|
||||
- And the opening square bracket `pattern:'['`.
|
||||
|
||||
In other words, all special charactere are allowed except where they mean something for square brackets.
|
||||
|
||||
A dot `"."` inside square brackets means just a dot. The pattern `pattern:[.,]` would look for one of characters: either a dot or a comma.
|
||||
|
||||
In the example below the regexp `pattern:[-().^+]` looks for one of the characters `-().^`:
|
||||
|
||||
```js run
|
||||
// No need to escape
|
||||
let reg = /[-().^+]/g;
|
||||
|
||||
alert( "1 + 2 - 3".match(reg) ); // Matches +, -
|
||||
```
|
||||
|
||||
...But if you decide to escape them "just in case", then there would be no harm:
|
||||
|
||||
```js run
|
||||
// Escaped everything
|
||||
let reg = /[\-\(\)\.\^\+]/g;
|
||||
|
||||
alert( "1 + 2 - 3".match(reg) ); // also works: +, -
|
||||
```
|
|
@ -0,0 +1,69 @@
|
|||
|
||||
# The unicode flag
|
||||
|
||||
The unicode flag `/.../u` enables the correct support of surrogate pairs.
|
||||
|
||||
Surrogate pairs are explained in the chapter <info:string>.
|
||||
|
||||
Let's briefly remind them here. In short, normally characters are encoded with 2 bytes. That gives us 65536 characters maximum. But there are more characters in the world.
|
||||
|
||||
So certain rare characters are encoded with 4 bytes, like `𝒳` (mathematical X) or `😄` (a smile).
|
||||
|
||||
Here are the unicode values to compare:
|
||||
|
||||
| Character | Unicode | Bytes |
|
||||
|------------|---------|--------|
|
||||
| `a` | 0x0061 | 2 |
|
||||
| `≈` | 0x2248 | 2 |
|
||||
|`𝒳`| 0x1d4b3 | 4 |
|
||||
|`𝒴`| 0x1d4b4 | 4 |
|
||||
|`😄`| 0x1f604 | 4 |
|
||||
|
||||
So characters like `a` and `≈` occupy 2 bytes, and those rare ones take 4.
|
||||
|
||||
The unicode is made in such a way that the 4-byte characters only have a meaning as a whole.
|
||||
|
||||
In the past Javascript did not know about that, and many string methods still have problems. For instance, `length` thinks that here are two characters:
|
||||
|
||||
```js run
|
||||
alert('😄'.length); // 2
|
||||
alert('𝒳'.length); // 2
|
||||
```
|
||||
|
||||
...But we can see that there's only one, right? The point is that `length` treats 4 bytes as two 2-byte characters. That's incorrect, because they must be considered only together (so-called "surrogate pair").
|
||||
|
||||
Normally, regular expressions also treat "long characters" as two 2-byte ones.
|
||||
|
||||
That leads to odd results, for instance let's try to find `pattern:[𝒳𝒴]` in the string `subject:𝒳`:
|
||||
|
||||
```js run
|
||||
alert( '𝒳'.match(/[𝒳𝒴]/) ); // odd result
|
||||
```
|
||||
|
||||
The result would be wrong, because by default the regexp engine does not understand surrogate pairs. It thinks that `[𝒳𝒴]` are not two, but four characters: the left half of `𝒳` `(1)`, the right half of `𝒳` `(2)`, the left half of `𝒴` `(3)`, the right half of `𝒴` `(4)`.
|
||||
|
||||
So it finds the left half of `𝒳` in the string `𝒳`, not the whole symbol.
|
||||
|
||||
In other words, the search works like `'12'.match(/[1234]/)` -- the `1` is returned (left half of `𝒳`).
|
||||
|
||||
The `/.../u` flag fixes that. It enables surrogate pairs in the regexp engine, so the result is correct:
|
||||
|
||||
```js run
|
||||
alert( '𝒳'.match(/[𝒳𝒴]/u) ); // 𝒳
|
||||
```
|
||||
|
||||
There's an error that may happen if we forget the flag:
|
||||
|
||||
```js run
|
||||
'𝒳'.match(/[𝒳-𝒴]/); // SyntaxError: invalid range in character class
|
||||
```
|
||||
|
||||
Here the regexp `[𝒳-𝒴]` is treated as `[12-34]` (where `2` is the right part of `𝒳` and `3` is the left part of `𝒴`), and the range between two halves `2` and `3` is unacceptable.
|
||||
|
||||
Using the flag would make it work right:
|
||||
|
||||
```js run
|
||||
alert( '𝒴'.match(/[𝒳-𝒵]/u) ); // 𝒴
|
||||
```
|
||||
|
||||
To finalize, let's note that if we do not deal with surrogate pairs, then the flag does nothing for us. But in the modern world we often meet them.
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
Solution:
|
||||
|
||||
```js run
|
||||
let reg = /\.{3,}/g;
|
||||
alert( "Hello!... How goes?.....".match(reg) ); // ..., .....
|
||||
```
|
||||
|
||||
Please note that the dot is a special character, so we have to escape it and insert as `\.`.
|
|
@ -0,0 +1,14 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# How to find an ellipsis "..." ?
|
||||
|
||||
Create a regexp to find ellipsis: 3 (or more?) dots in a row.
|
||||
|
||||
Check it:
|
||||
|
||||
```js
|
||||
let reg = /your regexp/g;
|
||||
alert( "Hello!... How goes?.....".match(reg) ); // ..., .....
|
||||
```
|
|
@ -0,0 +1,32 @@
|
|||
Итак, нужно написать выражение для описания цвета, который начинается с "#", за которым следуют 6 шестнадцатеричных символов.
|
||||
|
||||
Шестнадцатеричный символ можно описать с помощью `pattern:[0-9a-fA-F]`. Мы можем сократить выражение, используя не чувствительный к регистру шаблон `pattern:[0-9a-f]`.
|
||||
|
||||
Для его шестикратного повторения мы будем использовать квантификатор `pattern:{6}`.
|
||||
|
||||
В итоге, получаем выражение вида `pattern:/#[a-f0-9]{6}/gi`.
|
||||
|
||||
```js run
|
||||
var re = /#[a-f0-9]{6}/gi;
|
||||
|
||||
var str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2";
|
||||
|
||||
alert( str.match(re) ); // #121212,#AA00ef
|
||||
```
|
||||
|
||||
Проблема этого выражения в том, что оно находит цвет и в более длинных последовательностях:
|
||||
|
||||
```js run
|
||||
alert( "#12345678".match( /#[a-f0-9]{6}/gi ) ) // #12345678
|
||||
```
|
||||
|
||||
Чтобы такого не было, можно добавить в конец `\b`:
|
||||
|
||||
```js run
|
||||
// цвет
|
||||
alert( "#123456".match( /#[a-f0-9]{6}\b/gi ) ); // #123456
|
||||
|
||||
// не цвет
|
||||
alert( "#12345678".match( /#[a-f0-9]{6}\b/gi ) ); // null
|
||||
```
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# Регулярное выражение для цвета
|
||||
|
||||
Напишите регулярное выражение для поиска HTML-цвета, заданного как `#ABCDEF`, то есть `#` и содержит затем 6 шестнадцатеричных символов.
|
||||
|
||||
Пример использования:
|
||||
|
||||
```
|
||||
var re = /*...ваше регулярное выражение...*/
|
||||
|
||||
var str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2"
|
||||
|
||||
alert( str.match(re) ) // #121212,#AA00ef
|
||||
```
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
|
||||
Целое число -- это `pattern:\d+`.
|
||||
|
||||
Десятичная точка с дробной частью -- `pattern:\.\d+`.
|
||||
|
||||
Она не обязательна, так что обернём её в скобки с квантификатором `pattern:'?'`.
|
||||
|
||||
Итого, получилось регулярное выражение `pattern:\d+(\.\d+)?`:
|
||||
|
||||
```js run
|
||||
var re = /\d+(\.\d+)?/g
|
||||
|
||||
var str = "1.5 0 12. 123.4.";
|
||||
|
||||
alert( str.match(re) ); // 1.5, 0, 12, 123.4
|
||||
```
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Найдите положительные числа
|
||||
|
||||
Создайте регэксп, который ищет все положительные числа, в том числе и с десятичной точкой.
|
||||
|
||||
Пример использования:
|
||||
|
||||
```js
|
||||
var re = /* ваш регэксп */
|
||||
|
||||
var str = "1.5 0 12. 123.4.";
|
||||
|
||||
alert( str.match(re) ); // 1.5, 0, 12, 123.4
|
||||
```
|
|
@ -0,0 +1,12 @@
|
|||
Целое число с необязательной дробной частью -- это `pattern:\d+(\.\d+)?`.
|
||||
|
||||
К этому нужно добавить необязательный `-` в начале:
|
||||
|
||||
```js run
|
||||
var re = /-?\d+(\.\d+)?/g
|
||||
|
||||
var str = "-1.5 0 2 -123.4.";
|
||||
|
||||
alert( str.match(re) ); // -1.5, 0, 2, -123.4
|
||||
```
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Найдите десятичные числа
|
||||
|
||||
Создайте регэксп, который ищет все числа, в том числе и с десятичной точкой, в том числе и отрицательные.
|
||||
|
||||
Пример использования:
|
||||
|
||||
```js
|
||||
var re = /* ваш регэксп */
|
||||
|
||||
var str = "-1.5 0 2 -123.4.";
|
||||
|
||||
alert( str.match(re) ); // -1.5, 0, 2, -123.4
|
||||
```
|
|
@ -0,0 +1,133 @@
|
|||
# Quantifiers +, *, ? and {n}
|
||||
|
||||
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 in not digits, but full numbers: `7, 903, 123, 45, 67`.
|
||||
|
||||
A number is a sequence of 1 or more digits `\d`. The instrument to say how many we need is called *quantifiers*.
|
||||
|
||||
## Quantity {n}
|
||||
|
||||
The most obvious quantifier is a number in figure quotes: `pattern:{n}`. A quantifier is put after a character (or a character class and so on) and specifies exactly how many we need.
|
||||
|
||||
It also has advanced forms, here we go with examples:
|
||||
|
||||
Exact count: `{5}`
|
||||
: `pattern:\d{5}` denotes exactly 5 digits, the same as `pattern:\d\d\d\d\d`.
|
||||
|
||||
The example below looks for a 5-digit number:
|
||||
|
||||
```js run
|
||||
alert( "I'm 12345 years old".match(/\d{5}/) ); // "12345"
|
||||
```
|
||||
|
||||
We can add `\b` to exclude longer numbers: `pattern:\b\d{5}\b`.
|
||||
|
||||
The count from-to: `{3,5}`
|
||||
: To find numbers from 3 to 5 digits we can put the limits into figure brackets: `pattern:\d{3,5}`
|
||||
|
||||
```js run
|
||||
alert( "I'm not 12, but 1234 years old".match(/\d{3,5}/) ); // "1234"
|
||||
```
|
||||
|
||||
We can omit the upper limit. Then a regexp `pattern:\d{3,}` looks for numbers of `3` and more digits:
|
||||
|
||||
```js run
|
||||
alert( "I'm not 12, but 345678 years old".match(/\d{3,}/) ); // "345678"
|
||||
```
|
||||
|
||||
In case with the string `+7(903)-123-45-67` we need numbers: one or more digits in a row. That is `pattern:\d{1,}`:
|
||||
|
||||
```js run
|
||||
let str = "+7(903)-123-45-67";
|
||||
|
||||
let numbers = str.match(/\d{1,}/g);
|
||||
|
||||
alert(numbers); // 7,903,123,45,67
|
||||
```
|
||||
|
||||
## Shorthands
|
||||
|
||||
Most often needed quantifiers have shorthands:
|
||||
|
||||
`+`
|
||||
: Means "one or more", the same as `{1,}`.
|
||||
|
||||
For instance, `pattern:\d+` looks for numbers:
|
||||
|
||||
```js run
|
||||
let str = "+7(903)-123-45-67";
|
||||
|
||||
alert( str.match(/\d+/g) ); // 7,903,123,45,67
|
||||
```
|
||||
|
||||
`?`
|
||||
: Means "zero or one", the same as `{0,1}`. In other words, it makes the symbol optional.
|
||||
|
||||
For instance, the pattern `pattern:ou?r` looks for `match:o` followed by zero or one `match:u`, and then `match:r`.
|
||||
|
||||
So it can find `match:or` in the word `subject:color` and `match:our` in `subject:colour`:
|
||||
|
||||
```js run
|
||||
let str = "Should I write color or colour?";
|
||||
|
||||
alert( str.match(/colou?r/g) ); // color, colour
|
||||
```
|
||||
|
||||
`*`
|
||||
: Means "zero or more", the same as `{0,}`. That is, the character may repeat any times or be absent.
|
||||
|
||||
The example below looks for a digit followed by any number of zeroes:
|
||||
|
||||
```js run
|
||||
alert( "100 10 1".match(/\d0*/g) ); // 100, 10, 1
|
||||
```
|
||||
|
||||
Compare it with `'+'` (one or more):
|
||||
|
||||
```js run
|
||||
alert( "100 10 1".match(/\d0+/g) ); // 100, 10
|
||||
```
|
||||
|
||||
## More examples
|
||||
|
||||
Quantifiers are used very often. They are one of the main "building blocks" for complex regular expressions, so let's see more examples.
|
||||
|
||||
Regexp "decimal fraction" (a number with a floating point): `pattern:\d+\.\d+`
|
||||
: In action:
|
||||
```js run
|
||||
alert( "0 1 12.345 7890".match(/\d+\.\d+/g) ); // 12.345
|
||||
```
|
||||
|
||||
Regexp "open HTML-tag without attributes", like `<span>` or `<p>`: `pattern:/<[a-z]+>/i`
|
||||
: In action:
|
||||
|
||||
```js run
|
||||
alert( "<body> ... </body>".match(/<[a-z]+>/gi) ); // <body>
|
||||
```
|
||||
|
||||
We look for character `pattern:'<'` followed by one or more English letters, and then `pattern:'>'`.
|
||||
|
||||
Regexp "open HTML-tag without attributes" (improved): `pattern:/<[a-z][a-z0-9]*>/i`
|
||||
: Better regexp: according to the standard, HTML tag name may have a digit at any position except the first one, like `<h1>`.
|
||||
|
||||
```js run
|
||||
alert( "<h1>Hi!</h1>".match(/<[a-z][a-z0-9]*>/gi) ); // <h1>
|
||||
```
|
||||
|
||||
Regexp "opening or closing HTML-tag without attributes": `pattern:/<\/?[a-z][a-z0-9]*>/i`
|
||||
: We added an optional slash `pattern:/?` before the tag. Had to escape it with a backslash, otherwise Javascript would think it is the pattern end.
|
||||
|
||||
```js run
|
||||
alert( "<h1>Hi!</h1>".match(/<\/?[a-z][a-z0-9]*>/gi) ); // <h1>, </h1>
|
||||
```
|
||||
|
||||
```smart header="More precise means more complex"
|
||||
We can see one common rule in these examples: the more precise is the regular expression -- the longer and more complex it is.
|
||||
|
||||
For instance, HTML tags could use a simpler regexp: `pattern:<\w+>`.
|
||||
|
||||
Because `pattern:\w` means any English letter or a digit or `'_'`, the regexp also matches non-tags, for instance `match:<_>`. But it's much simpler than `pattern:<[a-z][a-z0-9]*>`.
|
||||
|
||||
Are we ok with `pattern:<\w+>` or we need `pattern:<[a-z][a-z0-9]*>`?
|
||||
|
||||
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.
|
||||
```
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
Результат: `123 456`.
|
||||
|
||||
Ленивый `\d+?` будет брать цифры до пробела, то есть `123`. После каждой цифры он будет останавливаться, проверять -- не пробел ли дальше? Если нет -- брать ещё цифру, в итоге возьмёт `123`.
|
||||
|
||||
Затем в дело вступит `\d+`, который по-максимуму возьмёт дальнейшие цифры, то есть `456`.
|
|
@ -0,0 +1,8 @@
|
|||
# Совпадение для /d+? d+/
|
||||
|
||||
Что будет при таком поиске, когда сначала стоит ленивый, а потом жадный квантификаторы?
|
||||
|
||||
```js
|
||||
"123 456".match(/\d+? \d+/g) ); // какой результат?
|
||||
```
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
Они очень похожи и, да, *почти* одинаковы. Оба ищут от одной кавычки до другой.
|
||||
|
||||
Различие здесь в символе точка `pattern:'.'`. Как мы помним, точка `pattern:'.'` обозначает *любой символ, кроме перевода строки*.
|
||||
|
||||
А `pattern:[^"]` -- это *любой символ, кроме кавычки `pattern:'"'`.
|
||||
|
||||
Получается, что первый регэксп `pattern:"[^"]*"` найдёт закавыченные строки с `\n` внутри, а второй регэксп `pattern:".*?"` -- нет.
|
||||
|
||||
Вот пример:
|
||||
```js run
|
||||
alert( '"многострочный \n текст"'.match(/"[^"]*"/) ); // найдёт
|
||||
|
||||
alert( '"многострочный \n текст"'.match(/".*?"/) ); // null (нет совпадений)
|
||||
```
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Различие между "[^"]*" и ".*?"
|
||||
|
||||
Регулярные выражения `pattern:"[^"]*"` и `pattern:".*?"` -- при выполнении одинаковы?
|
||||
|
||||
Иначе говоря, существует ли такая строка, на которой они дадут разные результаты? Если да -- дайте такую строку.
|
|
@ -0,0 +1,17 @@
|
|||
Нужно найти начало комментария `match:<!--`, затем всё до конца `match:-->`.
|
||||
|
||||
С первого взгляда кажется, что это сделает регулярное выражение `pattern:<!--.*?-->` -- квантификатор сделан ленивым, чтобы остановился, достигнув `match:-->`.
|
||||
|
||||
Однако, точка в JavaScript -- любой символ, *кроме* конца строки. Поэтому такой регэксп не найдёт многострочный комментарий.
|
||||
|
||||
Всё получится, если вместо точки использовать полностю "всеядный" `pattern:[\s\S]`.
|
||||
|
||||
Итого:
|
||||
|
||||
```js run
|
||||
var re = /<!--[\s\S]*?-->/g;
|
||||
|
||||
var str = '.. <!-- Мой -- комментарий \n тест --> .. <!----> .. ';
|
||||
|
||||
alert( str.match(re) ); // '<!-- Мой -- комментарий \n тест -->', '<!---->'
|
||||
```
|
|
@ -0,0 +1,12 @@
|
|||
# Найти HTML-комментарии
|
||||
|
||||
Найдите все HTML-комментарии в тексте:
|
||||
|
||||
```js
|
||||
var re = ..ваш регэксп..
|
||||
|
||||
var str = '.. <!-- Мой -- комментарий \n тест --> .. <!----> .. ';
|
||||
|
||||
alert( str.match(re) ); // '<!-- Мой -- комментарий \n тест -->', '<!---->'
|
||||
```
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
Начнём поиск с `pattern:<`, затем один или более произвольный символ, но до закрывающего "уголка": `pattern:.+?>`.
|
||||
|
||||
Проверим, как работает этот регэксп:
|
||||
|
||||
```js run
|
||||
var re = /<.+?>/g;
|
||||
|
||||
var str = '<> <a href="/"> <input type="radio" checked> <b>';
|
||||
|
||||
alert( str.match(re) ); // <> <a href="/">, <input type="radio" checked>, <b>
|
||||
```
|
||||
|
||||
Результат неверен! В качестве первого тега регэксп нашёл подстроку `match:<> <a href="/">`, но это явно не тег.
|
||||
|
||||
Всё потому, что `pattern:.+?` -- это "любой символ (кроме `\n`), повторяющийся один и более раз до того, как оставшаяся часть шаблона совпадёт (ленивость)".
|
||||
|
||||
Поэтому он находит первый `<`, затем есть "всё подряд" до следующего `>`.
|
||||
|
||||
Первое совпадение получается как раз таким:
|
||||
|
||||
```
|
||||
<.............>
|
||||
<> <a href="/"> <input type="radio" checked> <b>
|
||||
```
|
||||
|
||||
Правильным решением будет использовать `pattern:<[^>]+>`:
|
||||
|
||||
```js run
|
||||
var re = /<[^>]+>/g
|
||||
|
||||
var str = '<> <a href="/"> <input type="radio" checked> <b>';
|
||||
|
||||
alert( str.match(re) ); // <a href="/">, <input type="radio" checked>, <b>
|
||||
```
|
||||
|
||||
Это же решение автоматически позволяет находится внутри тегу символу `\n`, который в класс точка `.` не входит.
|
|
@ -0,0 +1,17 @@
|
|||
# Найти HTML-теги
|
||||
|
||||
Создайте регулярное выражение для поиска всех (открывающихся и закрывающихся) HTML-тегов вместе с атрибутами.
|
||||
|
||||
Пример использования:
|
||||
```js run
|
||||
var re = /* ваш регэксп */
|
||||
|
||||
var str = '<> <a href="/"> <input type="radio" checked> <b>';
|
||||
|
||||
alert( str.match(re) ); // '<a href="/">', '<input type="radio" checked>', '<b>'
|
||||
```
|
||||
|
||||
В этой задаче можно считать, что тег начинается с <code><</code>, заканчивается <code>></code> и может содержать внутри любые символы, кроме <code><</code> и <code>></code>.
|
||||
|
||||
Но хотя бы один символ внутри тега должен быть: <code><></code> -- не тег.
|
||||
|
|
@ -0,0 +1,308 @@
|
|||
# Жадные и ленивые квантификаторы
|
||||
|
||||
Квантификаторы -- с виду очень простая, но на самом деле очень хитрая штука.
|
||||
|
||||
Необходимо очень хорошо понимать, как именно происходит поиск, если конечно мы хотим искать что-либо сложнее чем `pattern:/\d+/`.
|
||||
|
||||
[cut]
|
||||
|
||||
Для примера рассмотрим задачу, которая часто возникает в типографике -- заменить в тексте кавычки вида `"..."` (их называют "английские кавычки") на "кавычки-ёлочки": `«...»`.
|
||||
|
||||
Для этого нужно сначала найти все слова в таких кавычках.
|
||||
|
||||
Соответствующее регулярное выражение может выглядеть так: `pattern:/".+"/g`, то есть мы ищем кавычку, после которой один или более произвольный символ, и в конце опять кавычка.
|
||||
|
||||
Однако, если попробовать применить его на практике, даже на таком простом случае...
|
||||
|
||||
```js run
|
||||
var reg = /".+"/g;
|
||||
|
||||
var str = 'a "witch" and her "broom" is one';
|
||||
|
||||
alert( str.match(reg) ); // "witch" and her "broom"
|
||||
```
|
||||
|
||||
...Мы увидим, что оно работает совсем не так, как задумано!
|
||||
|
||||
Вместо того, чтобы найти два совпадения `match:"witch"` и `match:"broom"`, оно находит одно: `match:"witch" and her "broom"`.
|
||||
|
||||
Это как раз тот случай, когда *жадность* -- причина всех зол.
|
||||
|
||||
## Жадный поиск
|
||||
|
||||
Чтобы найти совпадение, движок регулярных выражений обычно использует следующий алгоритм:
|
||||
|
||||
- Для каждой позиции в поисковой строке
|
||||
- Проверить совпадение на данной позиции
|
||||
- Посимвольно, с учётом классов и квантификаторов сопоставив с ней регулярное выражение.
|
||||
|
||||
Это общие слова, гораздо понятнее будет, если мы проследим, что именно он делает для регэкспа `pattern:".+"`.
|
||||
|
||||
1. Первый символ шаблона -- это кавычка `pattern:"`.
|
||||
|
||||
Движок регулярных выражений пытается сопоставить её на 0-й позиции в строке, но символ `a`, поэтому на 0-й позиции соответствия явно нет.
|
||||
|
||||
Далее он переходит 1ю, 2ю позицию в исходной строке и, наконец, обнаруживает кавычку на 3-й позиции:
|
||||
|
||||

|
||||
|
||||
2. Кавычка найдена, далее движок проверяет, есть ли соответствие для остальной части паттерна.
|
||||
|
||||
В данном случае следующий символ шаблона: `pattern:.` (точка). Она обозначает "любой символ", так что следующая буква строки `match:'w'` вполне подходит:
|
||||
|
||||

|
||||
|
||||
3. Далее "любой символ" повторяется, так как стоит квантификатор `pattern:.+`. Движок регулярных выражений берёт один символ за другим, до тех пор, пока у него это получается.
|
||||
|
||||
В данном случае это означает "до конца строки":
|
||||
|
||||

|
||||
|
||||
4. Итак, текст закончился, движок регулярных выражений больше не может найти "любой символ", он закончил повторения для `pattern:.+` и переходит к следующему символу шаблона.
|
||||
|
||||
Следующий символ шаблона -- это кавычка. Её тоже необходимо найти, чтобы соответствие было полным. А тут -- беда, ведь поисковый текст завершился!
|
||||
|
||||
Движок регулярных выражений понимает, что, наверное, взял многовато `pattern:.+` и начинает отступать обратно.
|
||||
|
||||
Иными словами, он сокращает текущее совпадение на один символ:
|
||||
|
||||

|
||||
|
||||
Это называется "фаза возврата" или "фаза бэктрекинга" (backtracking -- англ.).
|
||||
|
||||
Теперь `pattern:.+` соответствует почти вся оставшаяся строка, за исключением одного символа, и движок регулярных выражений ещё раз пытается подобрать соответствие для остатка шаблона, начиная с оставшейся части строки.
|
||||
|
||||
Если бы последним символом строки была кавычка `pattern:'"'`, то на этом бы всё и закончилось. Но последний символ `subject:'e'`, так что совпадения нет.
|
||||
|
||||
5. ...Поэтому движок уменьшает число повторений `pattern:.+` ещё на один символ:
|
||||
|
||||

|
||||
|
||||
Кавычка `pattern:'"'` не совпадает с `subject:'n'`. Опять неудача.
|
||||
|
||||
6. Движок продолжает отступать, он уменьшает количество повторений точки `pattern:'.'` до тех пор, пока остаток паттерна, то есть в данном случае кавычка `pattern:'"'`, не совпадёт:
|
||||
|
||||

|
||||
|
||||
7. Совпадение получено. Дальнейший поиск по оставшейся части строки `subject:is one` новых совпадений не даст.
|
||||
|
||||
Возможно, это не совсем то, что мы ожидали.
|
||||
|
||||
**В жадном режиме (по умолчанию) регэксп повторяет квантификатор настолько много раз, насколько это возможно, чтобы найти соответствие.**
|
||||
|
||||
То есть, любой символ `pattern:.+` повторился максимальное количество раз, что и привело к такой длинной строке.
|
||||
|
||||
А мы, наверное, хотели, чтобы каждая строка в кавычках была независимым совпадением? Для этого можно переключить квантификатор `+` в "ленивый" режим, о котором будет речь далее.
|
||||
|
||||
## Ленивый режим
|
||||
|
||||
Ленивый режим работы квантификаторов -- противоположность жадному, он означает "повторять минимальное количество раз".
|
||||
|
||||
Его можно включить, если поставить знак вопроса `pattern:'?'` после квантификатора, так что он станет таким: `pattern:*?` или `pattern:+?` или даже `pattern:??` для `pattern:'?'`.
|
||||
|
||||
Чтобы не возникло путаницы -- важно понимать: обычно `?` сам является квантификатором (ноль или один). Но если он стоит *после другого квантификатора (или даже после себя)*, то обретает другой смысл -- в этом случае он меняет режим его работы на ленивый.
|
||||
|
||||
Регэксп `pattern:/".+?"/g` работает, как задумано -- находит отдельно `match:witch` и `match:broom`:
|
||||
|
||||
```js run
|
||||
var reg = /".+?"/g;
|
||||
|
||||
var str = 'a "witch" and her "broom" is one';
|
||||
|
||||
alert( str.match(reg) ); // witch, broom
|
||||
```
|
||||
|
||||
Чтобы в точности понять, как поменялась работа квантификатора, разберём поиск по шагам.
|
||||
|
||||
1. Первый шаг -- тот же, кавычка `pattern:'"'` найдена на 3-й позиции:
|
||||
|
||||

|
||||
|
||||
2. Второй шаг -- тот же, находим произвольный символ `pattern:'.'`:
|
||||
|
||||

|
||||
|
||||
3. А вот дальше -- так как стоит ленивый режим работы `+`, то движок не повторит точку (произвольный символ) ещё раз, а останавливается на достигнутом и пытается проверить, есть ли соответствие остальной части шаблона, то есть `pattern:'"'`:
|
||||
|
||||

|
||||
|
||||
Если бы остальная часть шаблона на данной позиции совпала, то совпадение было бы найдено. Но в данном случае -- нет, символ `'i'` не равен '"'.
|
||||
4. Движок регулярных выражений увиличивает количество повторений точки на одно и пытается найти соответствие остатку шаблона ещё раз:
|
||||
|
||||

|
||||
Опять неудача. Тогда поисковой движок увеличивает количество повторений ещё и ещё...
|
||||
5. Только на пятом шаге поисковой движок наконец находит соответствие для остатка паттерна:
|
||||
|
||||

|
||||
6. Так как поиск происходит с флагом `g`, то он продолжается с конца текущего совпадения, давая ещё один результат:
|
||||
|
||||

|
||||
|
||||
В примере выше продемонстрирована работа ленивого режима для `pattern:+?`. Квантификаторы `pattern:+?` и `pattern:??` ведут себя аналогично -- "ленивый" движок увеличивает количество повторений только в том случае, если для остальной части шаблона на данной позиции нет соответствия.
|
||||
|
||||
**Ленивость распространяется только на тот квантификатор, после которого стоит `?`.**
|
||||
|
||||
Прочие квантификаторы остаются жадными.
|
||||
|
||||
Например:
|
||||
|
||||
```js run
|
||||
alert( "123 456".match(/\d+ \d+?/g) ); // 123 4
|
||||
```
|
||||
|
||||
1. Подшаблон `pattern:\d+` пытается найти столько цифр, сколько возможно (работает жадно), так что он находит `match:123` и останавливается, поскольку символ пробела `pattern:' '` не подходит под `pattern:\d`.
|
||||
2. Далее в шаблоне пробел, он совпадает.
|
||||
3. Далее в шаблоне идёт `pattern:\d+?`.
|
||||
|
||||
Квантификатор указан в ленивом режиме, поэтому он находит одну цифру `match:4` и пытается проверить, есть ли совпадение с остатком шаблона.
|
||||
|
||||
Но после `pattern:\d+?` в шаблоне ничего нет.
|
||||
|
||||
**Ленивый режим без необходимости лишний раз квантификатор не повторит.**
|
||||
|
||||
Так как шаблон завершился, то искать дальше, в общем-то нечего. Получено совпадение `match:123 4`.
|
||||
4. Следующий поиск продолжится с `5`, но ничего не найдёт.
|
||||
|
||||
```smart header="Конечные автоматы и не только"
|
||||
Современные движки регулярных выражений могут иметь более хитрую реализацию внутренних алгоритмов, чтобы искать быстрее.
|
||||
|
||||
Однако, чтобы понять, как работает регулярное выражение, и строить регулярные выражения самому, знание этих хитрых алгоритмов ни к чему. Они служат лишь внутренней оптимизации способа поиска, описанного выше.
|
||||
|
||||
Кроме того, сложные регулярные выражения плохо поддаются всяким оптимизациям, так что поиск вполне может работать и в точности как здесь описано.
|
||||
```
|
||||
|
||||
## Альтернативный подход
|
||||
|
||||
В данном конкретном случае, возможно искать строки в кавычках, оставаясь в жадном режиме, с использованием регулярного выражения `pattern:"[^"]+"`:
|
||||
|
||||
```js run
|
||||
var reg = /"[^"]+"/g;
|
||||
|
||||
var str = 'a "witch" and her "broom" is one';
|
||||
|
||||
alert( str.match(reg) ); // witch, broom
|
||||
```
|
||||
|
||||
Регэксп `pattern:"[^"]+"` даст правильные результаты, поскольку ищет кавычку `pattern:'"'`, за которой идут столько не-кавычек (исключающие квадратные скобки), сколько возможно.
|
||||
|
||||
Так что вторая кавычка автоматически прекращает повторения `pattern:[^"]+` и позволяет найти остаток шаблона `pattern:"`.
|
||||
|
||||
**Эта логика ни в коей мере не заменяет ленивые квантификаторы!**
|
||||
|
||||
Она просто другая. И то и другое бывает полезно.
|
||||
|
||||
Давайте посмотрим пример, когда нужен именно такой вариант, а ленивые квантификаторы не подойдут.
|
||||
|
||||
Например, мы хотим найти в тексте ссылки вида `<a href="..." class="doc">`, с любым содержанием `href`.
|
||||
|
||||
Какое регулярное выражение для этого подойдёт?
|
||||
|
||||
Первый вариант может выглядеть так: `pattern:/<a href=".*" class="doc">/g`.
|
||||
|
||||
Проверим его:
|
||||
```js run
|
||||
var str = '...<a href="link" class="doc">...';
|
||||
var reg = /<a href=".*" class="doc">/g;
|
||||
|
||||
// Сработало!
|
||||
alert( str.match(reg) ); // <a href="link" class="doc">
|
||||
```
|
||||
|
||||
А если в тексте несколько ссылок?
|
||||
|
||||
```js run
|
||||
var str = '...<a href="link1" class="doc">... <a href="link2" class="doc">...';
|
||||
var reg = /<a href=".*" class="doc">/g;
|
||||
|
||||
// Упс! Сразу две ссылки!
|
||||
alert( str.match(reg) ); // <a href="link1" class="doc">... <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
На этот раз результат неверен.
|
||||
|
||||
Жадный `pattern:.*` взял слишком много символов.
|
||||
|
||||
Соответствие получилось таким:
|
||||
```
|
||||
<a href="....................................." class="doc">
|
||||
<a href="link1" class="doc">... <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
Модифицируем шаблон -- добавим ленивость квантификатору `pattern:.*?`:
|
||||
|
||||
```js run
|
||||
var str = '...<a href="link1" class="doc">... <a href="link2" class="doc">...';
|
||||
var reg = /<a href=".*?" class="doc">/g;
|
||||
|
||||
// Сработало!
|
||||
alert( str.match(reg) ); // <a href="link1" class="doc">, <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
Теперь всё верно, два результата:
|
||||
|
||||
```
|
||||
<a href="....." class="doc"> <a href="....." class="doc">
|
||||
<a href="link1" class="doc">... <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
Почему теперь всё в порядке -- для внимательного читателя, после объяснений, данных выше в этой главе, должно быть полностью очевидно.
|
||||
|
||||
Поэтому не будем останавливаться здесь на деталях, а попробуем ещё пример:
|
||||
|
||||
```js run
|
||||
var str = '...<a href="link1" class="wrong">... <p style="" class="doc">...';
|
||||
var reg = /<a href=".*?" class="doc">/g;
|
||||
|
||||
// Неправильное совпадение!
|
||||
alert( str.match(reg) ); // <a href="link1" class="wrong">... <p style="" class="doc">
|
||||
```
|
||||
|
||||
Совпадение -- не ссылка, а более длинный текст.
|
||||
|
||||
Получилось следующее:
|
||||
|
||||
1. Найдено совпадение `match:<a href="`.
|
||||
2. Лениво ищем `pattern:.*?`, после каждого символа проверяя, есть ли совпадение остальной части шаблона.
|
||||
|
||||
Подшаблон `pattern:.*?` будет брать символы до тех пор, пока не найдёт `match:class="doc">`.
|
||||
|
||||
В данном случае этот поиск закончится уже за пределами ссылки, в теге `<p>`, вообще не имеющем отношения к `<a>`.
|
||||
3. Получившееся совпадение:
|
||||
|
||||
```
|
||||
<a href="..................................." class="doc">
|
||||
<a href="link1" class="wrong">... <p style="" class="doc">
|
||||
```
|
||||
|
||||
Итак, ленивость нам не помогла.
|
||||
|
||||
Необходимо как-то прекратить поиск `pattern:.*`, чтобы он не вышел за пределы кавычек.
|
||||
|
||||
Для этого мы используем более точное указание, какие символы нам подходят, а какие нет.
|
||||
|
||||
Правильный вариант: `pattern:[^"]*`. Этот шаблон будет брать все символы до ближайшей кавычки, как раз то, что требуется.
|
||||
|
||||
Рабочий пример:
|
||||
|
||||
```js run
|
||||
var str1 = '...<a href="link1" class="wrong">... <p style="" class="doc">...';
|
||||
var str2 = '...<a href="link1" class="doc">... <a href="link2" class="doc">...';
|
||||
var reg = /<a href="[^"]*" class="doc">/g;
|
||||
|
||||
// Работает!
|
||||
alert( str1.match(reg) ); // null, совпадений нет, и это верно
|
||||
alert( str2.match(reg) ); // <a href="link1" class="doc">, <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
## Итого
|
||||
|
||||
Квантификаторы имеют два режима работы:
|
||||
|
||||
Жадный
|
||||
: Режим по умолчанию -- движок регулярных выражений повторяет его по-максимуму. Когда повторять уже нельзя, например нет больше цифр для `\d+`, он продолжает поиск с оставшейся части текста. Если совпадение найти не удалось -- отступает обратно, уменьшая количество повторений.
|
||||
|
||||
Ленивый
|
||||
: При указании после квантификатора символа `?` он работает в ленивом режиме. То есть, он перед каждым повторением проверяет совпадение оставшейся части шаблона на текущей позиции.
|
||||
|
||||
Как мы видели в примере выше, ленивый режим -- не панацея от "слишком жадного" забора символов. Альтернатива -- более аккуратно настроенный "жадный", с исключением символов. Как мы увидим далее, можно исключать не только символы, но и целые подшаблоны.
|
||||
|
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 9.7 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 8 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 9.2 KiB |
After Width: | Height: | Size: 17 KiB |
|
@ -0,0 +1,29 @@
|
|||
Регулярное выражение для поиска 3-значного цвета вида `#abc`: `pattern:/#[a-f0-9]{3}/i`.
|
||||
|
||||
Нужно добавить ещё три символа, причём нужны именно три, четыре или семь символов не нужны. Эти три символа либо есть, либо нет.
|
||||
|
||||
Самый простой способ добавить -- просто дописать в конец регэкспа: `pattern:/#[a-f0-9]{3}([a-f0-9]{3})?/i`
|
||||
|
||||
Можно поступить и хитрее: `pattern:/#([a-f0-9]{3}){1,2}/i`.
|
||||
|
||||
Здесь регэксп `pattern:[a-f0-9]{3}` заключён в скобки, чтобы квантификатор `pattern:{1,2}` применялся целиком ко всей этой структуре.
|
||||
|
||||
В действии:
|
||||
```js run
|
||||
var re = /#([a-f0-9]{3}){1,2}/gi;
|
||||
|
||||
var str = "color: #3f3; background-color: #AA00ef; and: #abcd";
|
||||
|
||||
alert( str.match(re) ); // #3f3 #AA0ef #abc
|
||||
```
|
||||
|
||||
В последнем выражении `subject:#abcd` было найдено совпадение `match:#abc`. Чтобы этого не происходило, добавим в конец `pattern:\b`:
|
||||
|
||||
```js run
|
||||
var re = /#([a-f0-9]{3}){1,2}\b/gi;
|
||||
|
||||
var str = "color: #3f3; background-color: #AA00ef; and: #abcd";
|
||||
|
||||
alert( str.match(re) ); // #3f3 #AA0ef
|
||||
```
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# Найдите цвет в формате #abc или #abcdef
|
||||
|
||||
Напишите регулярное выражение, которое находит цвет в формате `#abc` или `#abcdef`. То есть, символ `#`, после которого идут 3 или 6 шестнадцатиричных символа.
|
||||
|
||||
Пример использования:
|
||||
```js
|
||||
var re = /* ваш регэксп */
|
||||
|
||||
var str = "color: #3f3; background-color: #AA00ef; and: #abcd";
|
||||
|
||||
alert( str.match(re) ); // #3f3 #AA0ef
|
||||
```
|
||||
|
||||
P.S. Значения из любого другого количества букв, кроме 3 и 6, такие как `#abcd`, не должны подходить под регэксп.
|
|
@ -0,0 +1,49 @@
|
|||
Регулярное выражение для числа, возможно, дробного и отрицательного: `pattern:-?\d+(\.\d+)?`. Мы уже разбирали его в предыдущих задачах.
|
||||
|
||||
Оператор -- это `pattern:[-+*/]`. Заметим, что дефис `pattern:-` идёт в списке первым, так как на любой позиции, кроме первой и последней, он имеет специальный смысл внутри `pattern:[...]`, и его понадобилось бы экранировать.
|
||||
|
||||
Кроме того, когда мы оформим это в JavaScript-синтаксис `pattern:/.../` -- понадобится заэкранировать слэш `pattern:/`.
|
||||
|
||||
Нам нужно число, затем оператор, затем число, и необязательные пробелы между ними.
|
||||
|
||||
Полное регулярное выражение будет таким: `pattern:-?\d+(\.\d+)?\s*[-+*/]\s*-?\d+(\.\d+)?`.
|
||||
|
||||
Чтобы получить результат в виде массива, добавим скобки вокруг тех данных, которые нам интересны, то есть -- вокруг чисел и оператора: `pattern:(-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?)`.
|
||||
|
||||
Посмотрим в действии:
|
||||
```js run
|
||||
var re = /(-?\d+(\.\d+)?)\s*([-+*\/])\s*(-?\d+(\.\d+)?)/;
|
||||
|
||||
alert( "1.2 + 12".match(re) );
|
||||
```
|
||||
|
||||
Итоговый массив будет включать в себя компоненты:
|
||||
|
||||
- `result[0] == "1.2 + 12"` (вначале всегда полное совпадение)
|
||||
- `result[1] == "1"` (первая скобка)
|
||||
- `result[2] == "2"` (вторая скобка -- дробная часть `(\.\d+)?`)
|
||||
- `result[3] == "+"` (...)
|
||||
- `result[4] == "12"` (...)
|
||||
- `result[5] == undefined` (последняя скобка, но у второго числа дробная часть отсутствует)
|
||||
|
||||
Нам из этого массива нужны только числа и оператор. А, скажем, дробная часть сама по себе -- не нужна.
|
||||
|
||||
Уберём её из запоминания, добавив в начало скобки `pattern:?:`, то есть: `pattern:(?:\.\d+)?`.
|
||||
|
||||
Итого, решение:
|
||||
|
||||
```js run
|
||||
function parse(expr) {
|
||||
var re = /(-?\d+(?:\.\d+)?)\s*([-+*\/])\s*(-?\d+(?:\.\d+)?)/;
|
||||
|
||||
var result = expr.match(re);
|
||||
|
||||
if (!result) return;
|
||||
result.shift();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
alert( parse("-1.23 * 3.45") ); // -1.23, *, 3.45
|
||||
```
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# Разобрать выражение
|
||||
|
||||
Арифметическое выражение состоит из двух чисел и операции между ними, например:
|
||||
|
||||
- `1 + 2`
|
||||
- `1.2 * 3.4`
|
||||
- `-3 / -6`
|
||||
- `-2 - 2`
|
||||
|
||||
Список операций: `"+"`, `"-"`, `"*"` и `"/"`.
|
||||
|
||||
Также могут присутствовать пробелы вокруг оператора и чисел.
|
||||
|
||||
Напишите функцию, которая будет получать выражение и возвращать массив из трёх аргументов:
|
||||
|
||||
1. Первое число.
|
||||
2. Оператор.
|
||||
3. Второе число.
|
||||
|
139
10-regular-expressions-javascript/08-regexp-groups/article.md
Normal file
|
@ -0,0 +1,139 @@
|
|||
# Скобочные группы
|
||||
|
||||
Часть шаблона может быть заключена в скобки `pattern:(...)`. Такие выделенные части шаблона называют "скобочными выражениями" или "скобочными группами".
|
||||
|
||||
У такого выделения есть два эффекта:
|
||||
|
||||
1. Он позволяет выделить часть совпадения в отдельный элемент массива при поиске через [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) или [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec).
|
||||
2. Если поставить квантификатор после скобки, то он применится *ко всей скобке*, а не всего лишь к одному символу.
|
||||
|
||||
[cut]
|
||||
|
||||
## Пример
|
||||
|
||||
В примере ниже, шаблон `pattern:(go)+` находит один или более повторяющихся `pattern:'go'`:
|
||||
|
||||
```js run
|
||||
alert( 'Gogogo now!'.match(/(go)+/i) ); // "Gogogo"
|
||||
```
|
||||
|
||||
Без скобок, шаблон `pattern:/go+/` означал бы `subject:g`, после которого идёт одна или более `subject:o`, например: `match:goooo`. А скобки "группируют" `pattern:(go)` вместе.
|
||||
|
||||
## Содержимое группы
|
||||
|
||||
Скобки нумеруются слева направо. Поисковой движок запоминает содержимое каждой скобки и позволяет обращаться к нему -- в шаблоне и строке замены и, конечно же, в результатах.
|
||||
|
||||
Например, найти HTML-тег можно шаблоном `pattern:<.*?>`.
|
||||
|
||||
После поиска мы захотим что-то сделать с результатом. Для удобства заключим содержимое `<...>` в скобки: `pattern:<(.*?)>`. Тогда оно будет доступно отдельно.
|
||||
|
||||
При поиске методом [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) в результирующем массиве будет сначала всё совпадение, а далее -- скобочные группы. В шаблоне `pattern:<(.*?)>` скобочная группа только одна:
|
||||
|
||||
```js run
|
||||
var str = '<h1>Привет, мир!</h1>';
|
||||
var reg = /<(.*?)>/;
|
||||
|
||||
alert( str.match(reg) ); // массив: <h1>, h1
|
||||
```
|
||||
|
||||
Заметим, что метод [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) выдаёт скобочные группы только при поиске без флага `/.../g`. В примере выше он нашёл только первое совпадение `match:<h1>`, а закрывающий `match:</h1>` не нашёл, поскольку без флага `/.../g` ищется только первое совпадение.
|
||||
|
||||
Для того, чтобы искать и с флагом `/.../g` и со скобочными группами, используется метод [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec):
|
||||
|
||||
```js run
|
||||
var str = '<h1>Привет, мир!</h1>';
|
||||
var reg = /<(.*?)>/g;
|
||||
|
||||
var match;
|
||||
|
||||
while ((match = reg.exec(str)) !== null) {
|
||||
// сначала выведет первое совпадение: <h1>,h1
|
||||
// затем выведет второе совпадение: </h1>,/h1
|
||||
alert(match);
|
||||
}
|
||||
```
|
||||
|
||||
Теперь найдено оба совпадения `pattern:<(.*?)>`, каждое -- массив из полного совпадения и скобочных групп (одна в данном случае).
|
||||
|
||||
## Вложенные группы
|
||||
Скобки могут быть и вложенными. В этом случае нумерация также идёт слева направо.
|
||||
|
||||
Например, при поиске тега в `subject:<span class="my">` нас может интересовать:
|
||||
|
||||
1. Содержимое тега целиком: `span class="my"`.
|
||||
2. В отдельную переменную для удобства хотелось бы поместить тег: `span`.
|
||||
3. Также может быть удобно отдельно выделить атрибуты `class="my"`.
|
||||
|
||||
Добавим скобки в регулярное выражение:
|
||||
|
||||
```js run
|
||||
var str = '<span class="my">';
|
||||
|
||||
var reg = /<(([a-z]+)\s*([^>]*))>/;
|
||||
|
||||
alert( str.match(reg) ); // <span class="my">, span class="my", span, class="my"
|
||||
```
|
||||
|
||||
Вот так выглядят скобочные группы:
|
||||
|
||||

|
||||
|
||||
На нулевом месте -- всегда совпадение полностью, далее -- группы. Нумерация всегда идёт слева направо, по открывающей скобке.
|
||||
|
||||
В данном случае получилось, что группа 1 включает в себя содержимое групп 2 и 3. Это совершенно нормальная ситуация, которая возникает, когда нужно выделить что-то отдельное внутри большей группы.
|
||||
|
||||
**Даже если скобочная группа необязательна и не входит в совпадение, соответствующий элемент массива существует (и равен `undefined`).**
|
||||
|
||||
Например, рассмотрим регэксп `pattern:a(z)?(c)?`. Он ищет `"a"`, за которой не обязательно идёт буква `"z"`, за которой необязательно идёт буква `"c"`.
|
||||
|
||||
Если напустить его на строку из одной буквы `"a"`, то результат будет таков:
|
||||
|
||||
```js run
|
||||
var match = 'a'.match(/a(z)?(c)?/)
|
||||
|
||||
alert( match.length ); // 3
|
||||
alert( match[0] ); // a
|
||||
alert( match[1] ); // undefined
|
||||
alert( match[2] ); // undefined
|
||||
```
|
||||
|
||||
Массив получился длины `3`, но все скобочные группы -- `undefined`.
|
||||
|
||||
А теперь более сложная ситуация, строка `subject:ack`:
|
||||
|
||||
```js run
|
||||
var match = 'ack'.match(/a(z)?(c)?/)
|
||||
|
||||
alert( match.length ); // 3
|
||||
alert( match[0] ); // ac, всё совпадение
|
||||
alert( match[1] ); // undefined, для (z)? ничего нет
|
||||
alert( match[2] ); // c
|
||||
```
|
||||
|
||||
Длина массива результатов по-прежнему `3`. Она постоянна. А вот для скобочной группы `pattern:(z)?` в ней ничего нет, поэтому результат: `["ac", undefined, "c"]`.
|
||||
|
||||
## Исключение из запоминания через ?:
|
||||
|
||||
Бывает так, что скобки нужны, чтобы квантификатор правильно применился, а вот запоминать их содержимое в массиве не нужно.
|
||||
|
||||
Скобочную группу можно исключить из запоминаемых и нумеруемых, добавив в её начало `pattern:?:`.
|
||||
|
||||
Например, мы хотим найти `pattern:(go)+`, но содержимое скобок (`go`) в отдельный элемент массива выделять не хотим.
|
||||
|
||||
Для этого нужно сразу после открывающей скобки поставить `?:`, то есть: `pattern:(?:go)+`.
|
||||
|
||||
Например:
|
||||
|
||||
```js run
|
||||
var str = "Gogo John!";
|
||||
*!*
|
||||
var reg = /(?:go)+ (\w+)/i;
|
||||
*/!*
|
||||
|
||||
var result = str.match(reg);
|
||||
|
||||
alert( result.length ); // 2
|
||||
alert( result[1] ); // John
|
||||
```
|
||||
|
||||
В примере выше массив результатов имеет длину `2` и содержит только полное совпадение и результат `pattern:(\w+)`. Это удобно в тех случаях, когда содержимое скобок нас не интересует.
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 25 KiB |
|
@ -0,0 +1,20 @@
|
|||
|
||||
Открывающий тег -- это `pattern:\[(b|url|quote)\]`.
|
||||
|
||||
Для того, чтобы найти всё до закрывающего -- используем ленивый поиск `pattern:[\s\S]*?` и обратную ссылку на открывающий тег.
|
||||
|
||||
Итого, получится: `pattern:\[(b|url|quote)\][\s\S]*?\[/\1\]`.
|
||||
|
||||
В действии:
|
||||
|
||||
```js run
|
||||
var re = /\[(b|url|quote)\][\s\S]*?\[\/\1\]/g;
|
||||
|
||||
var str1 = "..[url]http://ya.ru[/url]..";
|
||||
var str2 = "..[url][b]http://ya.ru[/b][/url]..";
|
||||
|
||||
alert( str1.match(re) ); // [url]http://ya.ru[/url]
|
||||
alert( str2.match(re) ); // [url][b]http://ya.ru[/b][/url]
|
||||
```
|
||||
|
||||
Для закрывающего тега `[/1]` понадобилось дополнительно экранировать слеш: `\[\/1\]`.
|
|
@ -0,0 +1,41 @@
|
|||
# Найдите пары тегов
|
||||
|
||||
ББ-тег имеет вид `[имя]...[/имя]`, где имя -- слово, одно из: `b`, `url`, `quote`.
|
||||
|
||||
Например:
|
||||
```
|
||||
[b]текст[/b]
|
||||
[url]http://ya.ru[/url]
|
||||
```
|
||||
|
||||
ББ-теги могут быть вложенными, но сам в себя тег быть вложен не может, например:
|
||||
|
||||
```
|
||||
Допустимо:
|
||||
[url] [b]http://ya.ru[/b] [/url]
|
||||
[quote] [b]текст[/b] [/quote]
|
||||
|
||||
Нельзя:
|
||||
[b][b]текст[/b][/b]
|
||||
```
|
||||
|
||||
Создайте регулярное выражение для поиска ББ-тегов и их содержимого.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
var re = /* регулярка */
|
||||
|
||||
var str = "..[url]http://ya.ru[/url]..";
|
||||
alert( str.match(re) ); // [url]http://ya.ru[/url]
|
||||
```
|
||||
|
||||
Если теги вложены, то нужно искать самый внешний тег (при желании можно будет продолжить поиск в его содержимом):
|
||||
|
||||
```js
|
||||
var re = /* регулярка */
|
||||
|
||||
var str = "..[url][b]http://ya.ru[/b][/url]..";
|
||||
alert( str.match(re) ); // [url][b]http://ya.ru[/b][/url]
|
||||
```
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
# Обратные ссылки: \n и $n
|
||||
|
||||
Скобочные группы можно не только получать в результате.
|
||||
|
||||
Движок регулярных выражений запоминает их содержимое, и затем его можно использовать как в самом паттерне, так и в строке замены.
|
||||
|
||||
[cut]
|
||||
|
||||
## Группа в строке замены
|
||||
|
||||
Ссылки в строке замены имеют вид `$n`, где `n` -- это номер скобочной группы.
|
||||
|
||||
Вместо `$n` подставляется содержимое соответствующей скобки:
|
||||
|
||||
```js run
|
||||
var name = "Александр Пушкин";
|
||||
|
||||
name = name.replace(/([а-яё]+) ([а-яё]+)/i, *!*"$2, $1"*/!*);
|
||||
alert( name ); // Пушкин, Александр
|
||||
```
|
||||
|
||||
В примере выше вместо `pattern:$2` подставляется второе найденное слово, а вместо `pattern:$1` -- первое.
|
||||
|
||||
## Группа в шаблоне
|
||||
|
||||
Выше был пример использования содержимого групп в строке замены. Это удобно, когда нужно реорганизовать содержимое или создать новое с использованием старого.
|
||||
|
||||
Но к скобочной группе можно также обратиться в самом поисковом шаблоне, ссылкой вида `\номер`.
|
||||
|
||||
Чтобы было яснее, рассмотрим это на реальной задаче -- необходимо найти в тексте строку в кавычках. Причём кавычки могут быть одинарными `subject:'...'` или двойными `subject:"..."` -- и то и другое должно искаться корректно.
|
||||
|
||||
Как такие строки искать?
|
||||
|
||||
Можно в регэкспе предусмотреть произвольные кавычки: `pattern:['"](.*?)['"]`. Такой регэксп найдёт строки вида `match:"..."`, `match:'...'`, но он даст неверный ответ в случае, если одна кавычка ненароком оказалась внутри другой, как например в строке `subject:"She's the one!"`:
|
||||
|
||||
```js run
|
||||
var str = "He said: \"She's the one!\".";
|
||||
|
||||
var reg = /['"](.*?)['"]/g;
|
||||
|
||||
// Результат не соответствует замыслу
|
||||
alert( str.match(reg) ); // "She'
|
||||
```
|
||||
|
||||
Как видно, регэксп нашёл открывающую кавычку `match:"`, затем текст, вплоть до новой кавычки `match:'`, которая закрывает соответствие.
|
||||
|
||||
Для того, чтобы попросить регэксп искать закрывающую кавычку -- такую же, как открывающую, мы обернём её в скобочную группу и используем обратную ссылку на неё:
|
||||
|
||||
```js run
|
||||
var str = "He said: \"She's the one!\".";
|
||||
|
||||
var reg = /(['"])(.*?)\1/g;
|
||||
|
||||
alert( str.match(reg) ); // "She's the one!"
|
||||
```
|
||||
|
||||
Теперь работает верно! Движок регулярных выражений, найдя первое скобочное выражение -- кавычку `pattern:(['"])`, запоминает его и далее `pattern:\1` означает "найти то же самое, что в первой скобочной группе".
|
||||
|
||||
Обратим внимание на два нюанса:
|
||||
|
||||
- Чтобы использовать скобочную группу в строке замены -- нужно использовать ссылку вида `$1`, а в шаблоне -- обратный слэш: `\1`.
|
||||
- Чтобы в принципе иметь возможность обратиться к скобочной группе -- не важно откуда, она не должна быть исключена из запоминаемых при помощи `?:`. Скобочные группы вида `(?:...)` не участвуют в нумерации.
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
Сначала неправильный способ.
|
||||
|
||||
Если перечислить языки один за другим через `|`, то получится совсем не то:
|
||||
|
||||
```js run
|
||||
var reg = /Java|JavaScript|PHP|C|C\+\+/g;
|
||||
|
||||
var str = "Java, JavaScript, PHP, C, C++";
|
||||
|
||||
alert( str.match(reg) ); // Java,Java,PHP,C,C
|
||||
```
|
||||
|
||||
Как видно, движок регулярных выражений ищет альтернации в порядке их перечисления. То есть, он сначала смотрит, есть ли `match:Java`, а если нет -- ищет `match:JavaScript`.
|
||||
|
||||
Естественно, при этом `match:JavaScript` не будет найдено никогда.
|
||||
|
||||
То же самое -- с языками `match:C` и `match:C++`.
|
||||
|
||||
Есть два решения проблемы:
|
||||
|
||||
1. Поменять порядок, чтобы более длинное совпадение проверялось первым: `pattern:JavaScript|Java|C\+\+|C|PHP`.
|
||||
2. Соединить длинный вариант с коротким: `pattern:Java(Script)?|C(\+\+)?|PHP`.
|
||||
|
||||
В действии:
|
||||
|
||||
```js run
|
||||
var reg = /Java(Script)?|C(\+\+)?|PHP/g;
|
||||
|
||||
var str = "Java, JavaScript, PHP, C, C++";
|
||||
|
||||
alert( str.match(reg) ); // Java,JavaScript,PHP,C,C++
|
||||
```
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# Найдите языки программирования
|
||||
|
||||
Существует много языков программирования, например Java, JavaScript, PHP, C, C++.
|
||||
|
||||
Напишите регулярное выражение, которое найдёт их все в строке "Java JavaScript PHP C++ C"
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
Решение задачи: `pattern:/"(\\.|[^"\\])*"/g`.
|
||||
|
||||
То есть:
|
||||
|
||||
- Сначала ищем кавычку `pattern:"`
|
||||
- Затем, если далее слэш `pattern:\\` (удвоение слэша -- техническое, для вставки в регэксп, на самом деле там один слэш), то после него также подойдёт любой символ (точка).
|
||||
- Если не слэш, то берём любой символ, кроме кавычек (которые будут означать конец строки) и слэша (чтобы предотвратить одинокие слэши, сам по себе единственный слэш не нужен, он должен экранировать какой-то символ) `pattern:[^"\\]`
|
||||
- ...И так жадно, до закрывающей кавычки.
|
||||
|
||||
В действии:
|
||||
|
||||
```js run
|
||||
var re = /"(\\.|[^"\\])*"/g;
|
||||
var str = '.. "test me" .. "Скажи \\"Привет\\"!" .. "\\r\\n\\\\" ..';
|
||||
|
||||
alert( str.match(re) ); // "test me","Скажи \"Привет\"!","\r\n\\"
|
||||
```
|
|
@ -0,0 +1,25 @@
|
|||
# Найдите строки в кавычках
|
||||
|
||||
Найдите в тексте при помощи регэкспа строки в двойных кавычках `subject:"..."`.
|
||||
|
||||
В строке поддерживается экранирование при помощи слеша -- примерно в таком же виде, как в обычных строках JavaScript. То есть, строка может содержать любые символы, экранированные слэшем, в частности: `subject:\"`, `subject:\n`, и даже сам слэш в экранированном виде: `subject:\\`.
|
||||
|
||||
Здесь особо важно, что двойная кавычка после слэша не оканчивает строку, а считается её частью. В этом и состоит основная сложность задачи, которая без этого условия была бы элементарной.
|
||||
|
||||
Пример совпадающих строк:
|
||||
```js
|
||||
.. *!*"test me"*/!* .. (обычная строка)
|
||||
.. *!*"Скажи \"Привет\"!"*/!* ... (строка с кавычками внутри)
|
||||
.. *!*"\r\n\\"*/!* .. (строка со спец. символами и слэшем внутри)
|
||||
```
|
||||
|
||||
Заметим, что в JavaScript такие строки удобнее всего задавать в одинарных кавычках, и слеши придётся удвоить (в одинарных кавычках они являются экранирующими символами):
|
||||
|
||||
Пример задания тестовой строки в JavaScript:
|
||||
```js run
|
||||
var str = ' .. "test me" .. "Скажи \\"Привет\\"!" .. "\\r\\n\\\\" .. ';
|
||||
|
||||
// эта строка будет такой:
|
||||
alert(str); // .. "test me" .. "Скажи \"Привет\"!" .. "\r\n\\" ..
|
||||
```
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
Начало шаблона очевидно: `pattern:<style`.
|
||||
|
||||
А вот дальше... Мы не можем написать просто `pattern:<style.*?>`, так как `match:<styler>` удовлетворяет этому регэкспу.
|
||||
|
||||
Нужно уточнить его. После `match:<style` должен быть либо пробел, после которого может быть что-то ещё, либо закрытие тега.
|
||||
|
||||
На языке регэкспов: `pattern:<style(>|\s.*?>)`.
|
||||
|
||||
В действии:
|
||||
|
||||
```js run
|
||||
var re = /<style(>|\s.*?>)/g;
|
||||
|
||||
alert( "<style> <styler> <style test>".match(re) ); // <style>, <style test>
|
||||
```
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# Найдите тег style
|
||||
|
||||
Напишите регулярное выражение, которое будет искать в тексте тег `<style>`. Подходят как обычный тег `<style>`, так и вариант с атрибутами `<style type="...">`.
|
||||
|
||||
Но регулярное выражение не должно находить `<styler>`!
|
||||
|
||||
Использование:
|
||||
|
||||
```js
|
||||
var re = ваш регэксп
|
||||
|
||||
alert( "<style> <styler> <style test>".match(re) ); // <style>, <style test>
|
||||
```
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# Альтернация (или) |
|
||||
|
||||
Альтернация -- термин в регулярных выражениях, которому в русском языке соответствует слово "ИЛИ". Она обозначается символом вертикальной черты `pattern:|` и позволяет выбирать между вариантами.
|
||||
|
||||
[cut]
|
||||
|
||||
Например, нам нужно найти языки программирования: HTML, PHP, Java и JavaScript.
|
||||
|
||||
Соответствующее регулярное выражение: `pattern:html|php|java(script)?`.
|
||||
|
||||
Пример использования:
|
||||
|
||||
```js run
|
||||
var reg = /html|php|css|java(script)?/gi
|
||||
|
||||
var str = "Сначала появился HTML, затем CSS, потом JavaScript"
|
||||
|
||||
alert( str.match(reg) ) // 'HTML', 'CSS', 'JavaScript'
|
||||
```
|
||||
|
||||
Мы уже знаем похожую вещь -- квадратные скобки. Они позволяют выбирать между символами, например `pattern:gr[ae]y` найдёт `match:gray`, либо `match:grey`.
|
||||
|
||||
Альтернация работает уже не посимвольно, а на уровне фраз и подвыражений. Регэксп `pattern:A|B|C` обозначает поиск одного из выражений: `A`, `B` или `C`, причём в качестве выражений могут быть другие, сколь угодно сложные регэкспы.
|
||||
|
||||
Для указания границ альтернации используют скобки `(...)`, например: `pattern:before(XXX|YYY)after` будет искать `match:beforeXXXafter` или `match:beforeYYYafter`.
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
Нам нужна строка, которая начинается -- и тут же кончается. То есть, пустая.
|
||||
|
||||
Или, если быть ближе к механике регэкспов, то движок сначала будет искать в тексте начальную позицию `pattern:^`, а как только найдёт её -- будет ожидать конечной `pattern:$`.
|
||||
|
||||
Заметим, что и `pattern:^` и `pattern:$` не требуют наличия символов. Это -- проверки. В пустой строке движок сначала проверит первую, а потом -- вторую -- и зафиксирует совпадение.
|
|
@ -0,0 +1,4 @@
|
|||
# Регэксп ^$
|
||||
|
||||
Предложите строку, которая подойдёт под регулярное выражение `pattern:^$`.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
Двузначное шестнадцатиричное число -- это `pattern:[0-9a-f]{2}` (с учётом флага `pattern:/i`).
|
||||
|
||||
Нам нужно одно такое число, и за ним ещё 5 с двоеточиями перед ними: `pattern:[0-9a-f]{2}(:[0-9a-f]{2}){5}`
|
||||
|
||||
И, наконец, совпадение должно начинаться в начале строки и заканчиваться -- в конце. То есть, строка целиком должна подходить под шаблон. Для этого обернём шаблон в `pattern:^...$`.
|
||||
|
||||
Итого, в действии:
|
||||
|
||||
```js run
|
||||
var re = /^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$/i;
|
||||
|
||||
alert( re.test('01:32:54:67:89:AB') ); // true
|
||||
|
||||
alert( re.test('0132546789AB') ); // false (нет двоеточий)
|
||||
|
||||
alert( re.test('01:32:54:67:89') ); // false (5 чисел, а не 6)
|
||||
|
||||
alert( re.test('01:32:54:67:89:ZZ') ) // false (ZZ в конце)
|
||||
```
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# Проверьте MAC-адрес
|
||||
|
||||
MAC-адрес сетевого интерфейса состоит из шести двузначных шестнадцатеричных чисел, разделённых двоеточием.
|
||||
|
||||
Например: `subject:'01:32:54:67:89:AB'`.
|
||||
|
||||
Напишите регулярное выражение, которое по строке проверяет, является ли она корректным MAC-адресом.
|
||||
|
||||
Использование:
|
||||
```js
|
||||
var re = ваш регэксп
|
||||
|
||||
alert( re.test('01:32:54:67:89:AB') ); // true
|
||||
|
||||
alert( re.test('0132546789AB') ); // false (нет двоеточий)
|
||||
|
||||
alert( re.test('01:32:54:67:89') ); // false (5 чисел, а не 6)
|
||||
|
||||
alert( re.test('01:32:54:67:89:ZZ') ) // false (ZZ в конце)
|
||||
```
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
# Начало строки ^ и конец $
|
||||
|
||||
Знак каретки `pattern:'^'` и доллара `pattern:'$'` имеют в регулярном выражении особый смысл. Их называют "якорями" (anchor - англ.).
|
||||
|
||||
[cut]
|
||||
|
||||
Каретка `pattern:^` совпадает в начале текста, а доллар `pattern:$` -- в конце.
|
||||
|
||||
**Якоря являются не символами, а проверками.**
|
||||
|
||||
До этого мы говорили о регулярных выражениях, которые ищут один или несколько символов. Если совпадение есть -- эти символы включаются в результат.
|
||||
|
||||
А якоря -- не такие. Когда поиск ходит до якоря -- он проверяет, есть ли соответствие, если есть -- продолжает идти по шаблону, не прибавляя ничего к результату.
|
||||
|
||||
Каретку `pattern:^` обычно используют, чтобы указать, что регулярное выражение необходимо проверить именно с начала текста.
|
||||
|
||||
Например, без каретки найдёт все числа:
|
||||
|
||||
```js run
|
||||
var str = '100500 попугаев съели 500100 бананов!';
|
||||
alert( str.match(/\d+/ig) ); // 100500, 500100 (нашло все числа)
|
||||
```
|
||||
|
||||
А с кареткой -- только первое:
|
||||
|
||||
```js run
|
||||
var str = '100500 попугаев съели 500100 бананов!';
|
||||
alert( str.match(/^\d+/ig) ); // 100500 (только в начале строки)*!*
|
||||
```
|
||||
|
||||
Знак доллара `pattern:$` используют, чтобы указать, что паттерн должен заканчиваться в конце текста.
|
||||
|
||||
Аналогичный пример с долларом для поиска числа в конце:
|
||||
|
||||
```js run
|
||||
var str = '100500 попугаев съели 500100';
|
||||
alert( str.match(/\d+$/ig) ); // 500100
|
||||
```
|
||||
|
||||
Оба якоря используют одновременно, если требуется, чтобы шаблон охватывал текст с начала и до конца. Обычно это требуется при валидации.
|
||||
|
||||
Например, мы хотим проверить, что в переменной `num` хранится именно десятичная дробь.
|
||||
|
||||
Ей соответствует регэксп `pattern:\d+\.\d+`. Но простой поиск найдёт дробь в любом тексте:
|
||||
|
||||
```js run
|
||||
var num = "ля-ля 12.34";
|
||||
alert( num.match(/\d+\.\d+/ig) ); // 12.34
|
||||
```
|
||||
|
||||
Наша же задача -- проверить, что `num` *целиком* соответствует паттерну `pattern:\d+\.\d+`.
|
||||
|
||||
Для этого обернём шаблон в якоря `pattern:^...$`:
|
||||
|
||||
```js run
|
||||
var num = "ля-ля 12.34";
|
||||
alert( num.match(/^\d+\.\d+$/ig) ); // null, не дробь
|
||||
|
||||
var num = "12.34";
|
||||
alert( num.match(/^\d+\.\d+$/ig) ); // 12.34, дробь!
|
||||
```
|
||||
|
||||
Теперь поиск ищет начало текста, за которым идёт число, затем точка, ещё число и конец текста. Это как раз то, что нужно.
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
# Многострочный режим, флаг "m"
|
||||
|
||||
Многострочный режим включается, если у регэкспа есть флаг `pattern:/m`.
|
||||
|
||||
[cut]
|
||||
|
||||
В этом случае изменяется поведение `pattern:^` и `pattern:$`.
|
||||
|
||||
В многострочном режиме якоря означают не только начало/конец текста, но и начало/конец строки.
|
||||
|
||||
## Начало строки ^
|
||||
|
||||
В примере ниже текст состоит из нескольких строк. Паттерн `pattern:/^\d+/gm` берёт число с начала каждой строки:
|
||||
|
||||
```js run
|
||||
var str = '1е место: Винни\n' +
|
||||
'2е место: Пятачок\n' +
|
||||
'33е место: Слонопотам';
|
||||
|
||||
*!*
|
||||
alert( str.match(/^\d+/gm) ); // 1, 2, 33
|
||||
*/!*
|
||||
```
|
||||
|
||||
Обратим внимание -- без флага `pattern:/m` было бы найдено только первое число:
|
||||
|
||||
```js run
|
||||
var str = '1е место: Винни\n' +
|
||||
'2е место: Пятачок\n' +
|
||||
'33е место: Слонопотам';
|
||||
|
||||
alert( str.match(/^\d+/g) ); // 1
|
||||
```
|
||||
|
||||
Это потому что в обычном режиме каретка `pattern:^` -- это только начало текста, а в многострочном -- начало любой строки.
|
||||
|
||||
Движок регулярных выражений двигается по тексту, и как только видит начало строки, начинает искать там `pattern:\d+`.
|
||||
|
||||
## Конец строки $
|
||||
|
||||
Символ доллара `pattern:$` ведёт себя аналогично.
|
||||
|
||||
Регулярное выражение `pattern:[а-я]+$` в следующем примере находит последнее слово в каждой строке:
|
||||
|
||||
```js run
|
||||
var str = '1е место: Винни\n' +
|
||||
'2е место: Пятачок\n' +
|
||||
'33е место: Слонопотам';
|
||||
|
||||
alert( str.match(/[а-я]+$/gim) ); // Винни,Пятачок,Слонопотам
|
||||
```
|
||||
|
||||
Без флага `pattern:m` якорь `pattern:$` обозначал бы конец всего текста, и было бы найдено только последнее слово.
|
||||
|
||||
````smart header="Якорь `$` против `\n`"
|
||||
Для того, чтобы найти конец строки, можно использовать не только `$`, но и символ `\n`.
|
||||
|
||||
Но, в отличие от `$`, символ `\n` во-первых берёт символ в результат, а во-вторых -- не совпадает в конце текста (если, конечно, последний символ -- не конец строки).
|
||||
|
||||
Посмотрим, что будет с примером выше, если вместо `pattern:[а-я]+$` использовать `pattern:[а-я]+\n`:
|
||||
|
||||
```js run
|
||||
var str = '1е место: Винни\n' +
|
||||
'2е место: Пятачок\n' +
|
||||
'33е место: Слонопотам';
|
||||
|
||||
alert( str.match(/[а-я]+\n/gim) );
|
||||
/*
|
||||
Винни
|
||||
,Пятачок
|
||||
*/
|
||||
```
|
||||
|
||||
Всего два результата: `match:Винни\n` (с символом перевода строки) и `match:Пятачок\n`. Последнее слово "Слонопотам" здесь не даёт совпадения, так как после него нет перевода строки.
|
||||
````
|
||||
|
||||
## Итого
|
||||
|
||||
В мультистрочном режиме:
|
||||
|
||||
- Символ `^` означает начало строки.
|
||||
- Символ `$` означает конец строки.
|
||||
|
||||
Оба символа являются проверками, они не добавляют ничего к результату. Про них также говорят, что "они имеют нулевую длину".
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# Предпросмотр (неготово)
|
||||
|
||||
Требуется добавить главу про предпросмотр lookahead.
|
||||
|
|
@ -0,0 +1,277 @@
|
|||
# Чёрная дыра бэктрекинга
|
||||
|
||||
Некоторые регулярные выражения, с виду являясь простыми, могут выполняться оооочень долго, и даже "подвешивать" интерпретатор JavaScript.
|
||||
|
||||
Рано или поздно, с этим сталкивается любой разработчик, потому что нечаянно создать такое регулярное выражение -- легче лёгкого.
|
||||
|
||||
Типична ситуация, когда регулярное выражение до поры до времени работает нормально, и вдруг на каком-то тексте как начнёт "подвешивать" интерпретатор и есть 100% процессора.
|
||||
|
||||
Это может стать уязвимостью. Например, если JavaScript выполняется на сервере, то при разборе данных, присланных посетителем, он может зависнуть, если использует подобный регэксп. На клиенте тоже возможно подобное, при использовании регэкспа для подсветки синтаксиса.
|
||||
|
||||
Такие уязвимости "убивали" почтовые сервера и системы обмена сообщениями и до появления JavaScript, и наверно будут "убивать" и после его исчезновения. Так что мы просто обязаны с ними разобраться.
|
||||
|
||||
[cut]
|
||||
|
||||
## Пример
|
||||
|
||||
План изложения у нас будет таким:
|
||||
|
||||
1. Сначала посмотрим на проблему в реальной ситуации.
|
||||
2. Потом упростим реальную ситуацию до "корней" и увидим, откуда она берётся.
|
||||
|
||||
Рассмотрим, например, поиск по HTML.
|
||||
|
||||
Мы хотим найти теги с атрибутами, то есть совпадения вида `subject:<a href="..." class=doc ...>`.
|
||||
|
||||
Самый простой способ это сделать -- `pattern:<[^>]*>`. Но он же и не совсем корректный, так как тег может выглядеть так: `subject:<a test="<>" href="#">`. То есть, внутри "закавыченного" атрибута может быть символ `>`. Простейший регэксп на нём остановится и найдёт `match:<a test="<>`.
|
||||
|
||||
Соответствие:
|
||||
```
|
||||
<[^>]*....>
|
||||
<a test="<>" href="#">
|
||||
```
|
||||
|
||||
А нам нужен весь тег.
|
||||
|
||||
Для того, чтобы правильно обрабатывать такие ситуации, нужно учесть их в регулярном выражении. Оно будет иметь вид `pattern:<тег (ключ=значение)*>`.
|
||||
|
||||
Если перевести на язык регэкспов, то: `pattern:<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>`:
|
||||
|
||||
1. `pattern:<\w+` -- начало тега
|
||||
2. `pattern:(\s*\w+=(\w+|"[^"]*")\s*)*` -- произвольное количество пар вида `слово=значение`, где "значение" может быть также словом `pattern:\w+`, либо строкой в кавычках `pattern:"[^"]*"`.
|
||||
|
||||
Мы пока не учитываем все детали грамматики HTML, ведь строки возможны и в 'одинарных' кавычках, но на данный момент этого достаточно. Главное, что регулярное выражение получилось в меру простым и понятным.
|
||||
|
||||
Испытаем полученный регэксп в действии:
|
||||
|
||||
```js run
|
||||
var reg = /<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>/g;
|
||||
|
||||
var str='...<a test="<>" href="#">... <b>...';
|
||||
|
||||
alert( str.match(reg) ); // <a test="<>" href="#">, <b>
|
||||
```
|
||||
|
||||
Отлично, всё работает! Нашло как длинный тег `match:<a test="<>" href="#">`, так и одинокий `match:<b>`.
|
||||
|
||||
А теперь -- демонстрация проблемы.
|
||||
|
||||
Если запустить пример ниже, то он может подвесить браузер:
|
||||
|
||||
```js run
|
||||
var reg = /<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>/g;
|
||||
|
||||
var str = "<tag a=b a=b a=b a=b a=b a=b a=b a=b \
|
||||
a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b";
|
||||
|
||||
*!*
|
||||
// Этот поиск будет выполняться очень, очень долго
|
||||
alert( str.match(reg) );
|
||||
*/!*
|
||||
```
|
||||
|
||||
Некоторые движки регулярных выражений могут в разумное время разобраться с таким поиском, но большинство -- нет.
|
||||
|
||||
В чём дело? Почему несложное регулярное выражение на такой небольшой строке "виснет" наглухо?
|
||||
|
||||
Упростим ситуацию, удалив тег и возможность указывать строки в кавычках:
|
||||
|
||||
```js run
|
||||
// только атрибуты, разделённые пробелами
|
||||
var reg = /<(\s*\w+=\w+\s*)*>/g;
|
||||
|
||||
var str = "<a=b a=b a=b a=b a=b a=b a=b a=b \
|
||||
a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b";
|
||||
|
||||
*!*
|
||||
// Этот поиск будет выполняться очень, очень долго
|
||||
alert( str.match(reg) );
|
||||
*/!*
|
||||
```
|
||||
|
||||
То же самое.
|
||||
|
||||
На этом мы закончим с демонстрацией "практического примера" и перейдём к разбору происходящего.
|
||||
|
||||
## Бектрекинг
|
||||
|
||||
В качестве ещё более простого регулярного выражения, рассмотрим `pattern:(\d+)*$`.
|
||||
|
||||
В большинстве движков регэкспов, например в Chrome или IE, этот поиск выполняется очень долго (осторожно, может "подвесить" браузер):
|
||||
|
||||
```js run
|
||||
alert( '12345678901234567890123456789123456789z'.match(/(\d+)*$/) );
|
||||
```
|
||||
|
||||
В чём же дело, что не так с регэкспом?
|
||||
|
||||
Внимательный читатель, посмотрев на него, наверняка удивится, ведь он "какой-то странный". Квантификатор `pattern:*` здесь выглядит лишним.
|
||||
|
||||
Если хочется найти число, то с тем же успехом можно искать `pattern:\d+$`.
|
||||
|
||||
Да, этот регэксп носит искусственный характер, но, разобравшись с ним, мы поймём и практический пример, данный выше. Причина их медленной работы одинакова.
|
||||
|
||||
В целом, с регэкспом "всё так", синтаксис вполне допустимый. Проблема в том, как выполняется поиск по нему.
|
||||
|
||||
Посмотрим, что происходит при поиске в строке `subject:123456789z`:
|
||||
|
||||
1. Первым делом, движок регэкспов пытается найти `pattern:\d+`. Плюс `pattern:+` является жадным по умолчанию, так что он хватает все цифры, какие может:
|
||||
|
||||
```
|
||||
\d+.......
|
||||
(123456789)z
|
||||
```
|
||||
2. Затем движок пытается применить звёздочку вокруг скобок `pattern:(\d+)*`, но больше цифр нет, так что звёздочка не даёт повторений.
|
||||
|
||||
Затем в шаблоне идёт символ конца строки `pattern:$`, а в тексте -- символ `subject:z`.
|
||||
|
||||
```
|
||||
X
|
||||
\d+........$
|
||||
(123456789)z
|
||||
```
|
||||
|
||||
Соответствия нет.
|
||||
3. Так как соответствие не найдено, то "жадный" плюс `pattern:+` отступает на один символ (бэктрекинг).
|
||||
|
||||
Теперь `\d+` -- это все цифры, за исключением последней:
|
||||
```
|
||||
\d+.......
|
||||
(12345678)9z
|
||||
```
|
||||
4. После бэктрекинга, `pattern:\d+` содержит всё число, кроме последней цифры. Движок снова пытается найти совпадение, уже с новой позиции (`9`).
|
||||
|
||||
Звёздочка `pattern:(\d+)*` теперь может быть применена -- она даёт число `match:9`:
|
||||
|
||||
```
|
||||
|
||||
\d+.......\d+
|
||||
(12345678)(9)z
|
||||
```
|
||||
|
||||
Движок пытается найти `$`, но это ему не удаётся -- на его пути опять `z`:
|
||||
|
||||
```
|
||||
X
|
||||
\d+.......\d+
|
||||
(12345678)(9)z
|
||||
```
|
||||
|
||||
Так как совпадения нет, то поисковой движок отступает назад ещё раз.
|
||||
5. Теперь первое число `pattern:\d+` будет содержать 7 цифр, а остаток строки `subject:89` становится вторым `pattern:\d+`:
|
||||
|
||||
```
|
||||
X
|
||||
\d+......\d+
|
||||
(1234567)(89)z
|
||||
```
|
||||
|
||||
Увы, всё ещё нет соответствия для `pattern:$`.
|
||||
|
||||
Поисковой движок снова должен отступить назад. При этом последний жадный квантификатор отпускает символ. В данном случае это означает, что укорачивается второй `pattern:\d+`, до одного символа `subject:8`, и звёздочка забирает следующий `subject:9`.
|
||||
|
||||
```
|
||||
X
|
||||
\d+......\d+\d+
|
||||
(1234567)(8)(9)z
|
||||
```
|
||||
6. ...И снова неудача. Второе и третье `pattern:\d+` отступили по-максимуму, так что сокращается снова первое число, до `subject:123456`, а звёздочка берёт оставшееся:
|
||||
|
||||
```
|
||||
X
|
||||
\d+.......\d+
|
||||
(123456)(789)z
|
||||
```
|
||||
|
||||
Снова нет совпадения. Процесс повторяется, последний жадный квантификатор `pattern:+` отпускает один символ (`9`):
|
||||
|
||||
```
|
||||
X
|
||||
\d+.....\d+ \d+
|
||||
(123456)(78)(9)z
|
||||
```
|
||||
7. ...И так далее.
|
||||
|
||||
Получается, что движок регулярных выражений перебирает все комбинации из `123456789` и их подпоследовательности. А таких комбинаций очень много.
|
||||
|
||||
На этом месте умный читатель может воскликнуть: "Во всём виноват бэктрекинг? Давайте включим ленивый режим -- и не будет никакого бэктрекинга!"
|
||||
|
||||
Что ж, заменим `pattern:\d+` на `pattern:\d+?` и посмотрим (аккуратно, может подвесить браузер):
|
||||
|
||||
```js run
|
||||
alert( '12345678901234567890123456789123456789z'.match(/(\d+?)*$/) );
|
||||
```
|
||||
|
||||
Не помогло!
|
||||
|
||||
**Ленивые регулярные выражения делают то же самое, но в обратном порядке.**
|
||||
|
||||
Просто подумайте о том, как будет в этом случае работать поисковой движок.
|
||||
|
||||
Некоторые движки регулярных выражений содержат хитрые проверки и конечные автоматы, которые позволяют избежать бесконечного перебора или кардинально ускорить его, но не все движки и не всегда.
|
||||
|
||||
Возвращаясь к примеру выше -- при поиске `pattern:<(\s*\w+=\w+\s*)*>` в строке `subject:<a=b a=b a=b a=b` происходит то же самое.
|
||||
|
||||
Поиск успешно начинается, выбирается некая комбинация из `pattern:\s*\w+=\w+\s*`, которая, так как в конце нет `>`, оказывается не подходящей. Движок честно отступает, пробует другую комбинацию -- и так далее.
|
||||
|
||||
## Что делать?
|
||||
|
||||
Проблема -- в сверхмноговариантном переборе.
|
||||
|
||||
Движок регулярных выражений перебирает кучу возможных вариантов скобок там, где это не нужно.
|
||||
|
||||
Например, в регэкспе `pattern:(\d+)*$` нам (людям) очевидно, что в `pattern:(\d+)` откатываться не нужно. От того, что вместо одного `pattern:\d+` у нас два независимых `pattern:\d+\d+`, ничего не изменится.
|
||||
|
||||
Без разницы:
|
||||
|
||||
```
|
||||
\d+........
|
||||
(123456789)z
|
||||
|
||||
\d+...\d+....
|
||||
(1234)(56789)z
|
||||
```
|
||||
|
||||
Если вернуться к более реальному примеру `pattern:<(\s*\w+=\w+\s*)*>` то
|
||||
сам алгоритм поиска, который у нас в голове, предусматривает, что мы "просто" ищем тег, а потом пары `атрибут=значение` (сколько получится).
|
||||
|
||||
Никакого "отката" здесь не нужно.
|
||||
|
||||
В современных регулярных выражениях для решения этой проблемы придумали "possessive" (сверхжадные? неоткатные? точный перевод пока не устоялся) квантификаторы, которые вообще не используют бэктрегинг.
|
||||
|
||||
То есть, они даже проще, чем "жадные" -- берут максимальное количество символов и всё. Поиск продолжается дальше. При несовпадении никакого возврата не происходит.
|
||||
|
||||
Это, с одной стороны, уменьшает количество возможных результатов, но, с другой стороны, в ряде случаев очевидно, что возврат (уменьшение количество повторений квантификатора) результата не даст. А только потратит время, что как раз и доставляет проблемы. Как раз такие ситуации и описаны выше.
|
||||
|
||||
Есть и другое средство -- "атомарные скобочные группы", которые запрещают перебор внутри скобок, по сути позволяя добиваться того же, что и сверхжадные квантификаторы,
|
||||
|
||||
К сожалению, в JavaScript они не поддерживаются.
|
||||
|
||||
Однако, можно получить подобный эффект при помощи предпросмотра. Подробное описание соответствия с учётом синтаксиса сверхжадных квантификаторов и атомарных групп есть в статьях [Regex: Emulate Atomic Grouping (and Possessive Quantifiers) with LookAhead](http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead) и [Mimicking Atomic Groups](http://blog.stevenlevithan.com/archives/mimic-atomic-groups), здесь же мы останемся в рамках синтаксиса JavaScript.
|
||||
|
||||
Взятие максимального количества повторений `a+` без отката выглядит так: `pattern:(?=(a+))\1`.
|
||||
|
||||
То есть, иными словами, предпросмотр `pattern:?=` ищет максимальное количество повторений `pattern:a+`, доступных с текущей позиции. А затем они "берутся в результат" обратной ссылкой `pattern:\1`. Дальнейший поиск -- после найденных повторений.
|
||||
|
||||
Откат в этой логике в принципе не предусмотрен, поскольку предпросмотр "откатываться" не умеет. То есть, если предпросмотр нашёл 5 штук `pattern:a+`, и в результате поиск не удался, то он не будет откатываться на 4 повторения. Эта возможность в предпросмотре отсутствует, а в данном случае она как раз и не нужна.
|
||||
|
||||
Исправим регэксп для поиска тега с атрибутами `pattern:<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>`, описанный в начале главы. Используем предпросмотр, чтобы запретить откат на меньшее количество пар `атрибут=значение`:
|
||||
|
||||
```js run
|
||||
// регэксп для пары атрибут=значение
|
||||
var attr = /(\s*\w+=(\w+|"[^"]*")\s*)/
|
||||
|
||||
// используем его внутри регэкспа для тега
|
||||
var reg = new RegExp('<\\w+(?=(' + attr.source + '*))\\1>', 'g');
|
||||
|
||||
var good = '...<a test="<>" href="#">... <b>...';
|
||||
|
||||
var bad = "<tag a=b a=b a=b a=b a=b a=b a=b a=b\
|
||||
a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b";
|
||||
|
||||
alert( good.match(reg) ); // <a test="<>" href="#">, <b>
|
||||
alert( bad.match(reg) ); // null (нет результатов, быстро)
|
||||
```
|
||||
|
||||
Отлично, всё работает! Нашло как длинный тег `match:<a test="<>" href="#">`, так и одинокий `match:<b>`.
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.4 KiB |
3
10-regular-expressions-javascript/index.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Regular expressions
|
||||
|
||||
Regular expressions is a powerful way of doing search and replace in strings.
|