diff --git a/10-regular-expressions-javascript/01-regexp-introduction/article.md b/10-regular-expressions-javascript/01-regexp-introduction/article.md new file mode 100644 index 00000000..6206c57c --- /dev/null +++ b/10-regular-expressions-javascript/01-regexp-introduction/article.md @@ -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 . + +`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. diff --git a/10-regular-expressions-javascript/02-regexp-methods/article.md b/10-regular-expressions-javascript/02-regexp-methods/article.md new file mode 100644 index 00000000..13dae08f --- /dev/null +++ b/10-regular-expressions-javascript/02-regexp-methods/article.md @@ -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| +|$`|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. diff --git a/10-regular-expressions-javascript/03-regexp-character-classes/1-find-time-hh-mm/solution.md b/10-regular-expressions-javascript/03-regexp-character-classes/1-find-time-hh-mm/solution.md new file mode 100644 index 00000000..829eda13 --- /dev/null +++ b/10-regular-expressions-javascript/03-regexp-character-classes/1-find-time-hh-mm/solution.md @@ -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 +``` diff --git a/10-regular-expressions-javascript/03-regexp-character-classes/1-find-time-hh-mm/task.md b/10-regular-expressions-javascript/03-regexp-character-classes/1-find-time-hh-mm/task.md new file mode 100644 index 00000000..5e32b9c4 --- /dev/null +++ b/10-regular-expressions-javascript/03-regexp-character-classes/1-find-time-hh-mm/task.md @@ -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`. diff --git a/10-regular-expressions-javascript/03-regexp-character-classes/article.md b/10-regular-expressions-javascript/03-regexp-character-classes/article.md new file mode 100644 index 00000000..6b8d8bcf --- /dev/null +++ b/10-regular-expressions-javascript/03-regexp-character-classes/article.md @@ -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): + +![](love-html5-classes.png) + +## 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`: + +![](hello-java-boundaries.png) + +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. diff --git a/10-regular-expressions-javascript/03-regexp-character-classes/hello-java-boundaries.png b/10-regular-expressions-javascript/03-regexp-character-classes/hello-java-boundaries.png new file mode 100644 index 00000000..ba293e6d Binary files /dev/null and b/10-regular-expressions-javascript/03-regexp-character-classes/hello-java-boundaries.png differ diff --git a/10-regular-expressions-javascript/03-regexp-character-classes/hello-java-boundaries@2x.png b/10-regular-expressions-javascript/03-regexp-character-classes/hello-java-boundaries@2x.png new file mode 100644 index 00000000..9ebf623b Binary files /dev/null and b/10-regular-expressions-javascript/03-regexp-character-classes/hello-java-boundaries@2x.png differ diff --git a/10-regular-expressions-javascript/03-regexp-character-classes/love-html5-classes.png b/10-regular-expressions-javascript/03-regexp-character-classes/love-html5-classes.png new file mode 100644 index 00000000..b8ed77c0 Binary files /dev/null and b/10-regular-expressions-javascript/03-regexp-character-classes/love-html5-classes.png differ diff --git a/10-regular-expressions-javascript/03-regexp-character-classes/love-html5-classes@2x.png b/10-regular-expressions-javascript/03-regexp-character-classes/love-html5-classes@2x.png new file mode 100644 index 00000000..9fb998b9 Binary files /dev/null and b/10-regular-expressions-javascript/03-regexp-character-classes/love-html5-classes@2x.png differ diff --git a/10-regular-expressions-javascript/035-regexp-escaping/article.md b/10-regular-expressions-javascript/035-regexp-escaping/article.md new file mode 100644 index 00000000..6a218c58 --- /dev/null +++ b/10-regular-expressions-javascript/035-regexp-escaping/article.md @@ -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()/\//) ); // '/' +``` diff --git a/10-regular-expressions-javascript/04-regexp-character-sets-and-ranges/1-find-range-1/solution.md b/10-regular-expressions-javascript/04-regexp-character-sets-and-ranges/1-find-range-1/solution.md new file mode 100644 index 00000000..a6d71f66 --- /dev/null +++ b/10-regular-expressions-javascript/04-regexp-character-sets-and-ranges/1-find-range-1/solution.md @@ -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" + ``` diff --git a/10-regular-expressions-javascript/04-regexp-character-sets-and-ranges/1-find-range-1/task.md b/10-regular-expressions-javascript/04-regexp-character-sets-and-ranges/1-find-range-1/task.md new file mode 100644 index 00000000..5a48e01e --- /dev/null +++ b/10-regular-expressions-javascript/04-regexp-character-sets-and-ranges/1-find-range-1/task.md @@ -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`? diff --git a/10-regular-expressions-javascript/04-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md b/10-regular-expressions-javascript/04-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md new file mode 100644 index 00000000..349743dd --- /dev/null +++ b/10-regular-expressions-javascript/04-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md @@ -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:'-'` не экранирован, поскольку в начале скобок он не может иметь специального смысла. diff --git a/10-regular-expressions-javascript/04-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md b/10-regular-expressions-javascript/04-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md new file mode 100644 index 00000000..dd03af58 --- /dev/null +++ b/10-regular-expressions-javascript/04-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md @@ -0,0 +1,11 @@ +# Найдите время в одном из форматов + +Время может быть в формате `часы:минуты` или `часы-минуты`. И часы и минуты состоят из двух цифр, например `09:00`, `21-30`. + +Напишите регулярное выражение для поиска времени: + +```js +var re = /ваше выражение/; +alert( "Завтрак в 09:00. Обед - в 21-30".match(re) ); // 09:00, 21-30 +``` + diff --git a/10-regular-expressions-javascript/04-regexp-character-sets-and-ranges/article.md b/10-regular-expressions-javascript/04-regexp-character-sets-and-ranges/article.md new file mode 100644 index 00000000..463b89b4 --- /dev/null +++ b/10-regular-expressions-javascript/04-regexp-character-sets-and-ranges/article.md @@ -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: +, - +``` diff --git a/10-regular-expressions-javascript/05-regexp-unicode/article.md b/10-regular-expressions-javascript/05-regexp-unicode/article.md new file mode 100644 index 00000000..39e6b46e --- /dev/null +++ b/10-regular-expressions-javascript/05-regexp-unicode/article.md @@ -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 . + +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. diff --git a/10-regular-expressions-javascript/06-regexp-quantifiers/1-find-text-manydots/solution.md b/10-regular-expressions-javascript/06-regexp-quantifiers/1-find-text-manydots/solution.md new file mode 100644 index 00000000..d4ddb136 --- /dev/null +++ b/10-regular-expressions-javascript/06-regexp-quantifiers/1-find-text-manydots/solution.md @@ -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 `\.`. diff --git a/10-regular-expressions-javascript/06-regexp-quantifiers/1-find-text-manydots/task.md b/10-regular-expressions-javascript/06-regexp-quantifiers/1-find-text-manydots/task.md new file mode 100644 index 00000000..6fd91bdc --- /dev/null +++ b/10-regular-expressions-javascript/06-regexp-quantifiers/1-find-text-manydots/task.md @@ -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) ); // ..., ..... +``` diff --git a/10-regular-expressions-javascript/06-regexp-quantifiers/2-find-html-colors-6hex/solution.md b/10-regular-expressions-javascript/06-regexp-quantifiers/2-find-html-colors-6hex/solution.md new file mode 100644 index 00000000..811381cf --- /dev/null +++ b/10-regular-expressions-javascript/06-regexp-quantifiers/2-find-html-colors-6hex/solution.md @@ -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 +``` + diff --git a/10-regular-expressions-javascript/06-regexp-quantifiers/2-find-html-colors-6hex/task.md b/10-regular-expressions-javascript/06-regexp-quantifiers/2-find-html-colors-6hex/task.md new file mode 100644 index 00000000..ea2dbf18 --- /dev/null +++ b/10-regular-expressions-javascript/06-regexp-quantifiers/2-find-html-colors-6hex/task.md @@ -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 +``` + diff --git a/10-regular-expressions-javascript/06-regexp-quantifiers/3-find-decimal-positive-numbers/solution.md b/10-regular-expressions-javascript/06-regexp-quantifiers/3-find-decimal-positive-numbers/solution.md new file mode 100644 index 00000000..fa6db2c0 --- /dev/null +++ b/10-regular-expressions-javascript/06-regexp-quantifiers/3-find-decimal-positive-numbers/solution.md @@ -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 +``` + diff --git a/10-regular-expressions-javascript/06-regexp-quantifiers/3-find-decimal-positive-numbers/task.md b/10-regular-expressions-javascript/06-regexp-quantifiers/3-find-decimal-positive-numbers/task.md new file mode 100644 index 00000000..e353cf43 --- /dev/null +++ b/10-regular-expressions-javascript/06-regexp-quantifiers/3-find-decimal-positive-numbers/task.md @@ -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 +``` diff --git a/10-regular-expressions-javascript/06-regexp-quantifiers/4-find-decimal-numbers/solution.md b/10-regular-expressions-javascript/06-regexp-quantifiers/4-find-decimal-numbers/solution.md new file mode 100644 index 00000000..417dc1b0 --- /dev/null +++ b/10-regular-expressions-javascript/06-regexp-quantifiers/4-find-decimal-numbers/solution.md @@ -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 +``` + diff --git a/10-regular-expressions-javascript/06-regexp-quantifiers/4-find-decimal-numbers/task.md b/10-regular-expressions-javascript/06-regexp-quantifiers/4-find-decimal-numbers/task.md new file mode 100644 index 00000000..01825ef3 --- /dev/null +++ b/10-regular-expressions-javascript/06-regexp-quantifiers/4-find-decimal-numbers/task.md @@ -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 +``` diff --git a/10-regular-expressions-javascript/06-regexp-quantifiers/article.md b/10-regular-expressions-javascript/06-regexp-quantifiers/article.md new file mode 100644 index 00000000..3259cc14 --- /dev/null +++ b/10-regular-expressions-javascript/06-regexp-quantifiers/article.md @@ -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 `` or `

`: `pattern:/<[a-z]+>/i` +: In action: + + ```js run + alert( " ... ".match(/<[a-z]+>/gi) ); // + ``` + + 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 `

`. + + ```js run + alert( "

Hi!

".match(/<[a-z][a-z0-9]*>/gi) ); //

+ ``` + +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( "

Hi!

".match(/<\/?[a-z][a-z0-9]*>/gi) ); //

,

+ ``` + +```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. +``` diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/1-lazy-greedy/solution.md b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/1-lazy-greedy/solution.md new file mode 100644 index 00000000..838534ee --- /dev/null +++ b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/1-lazy-greedy/solution.md @@ -0,0 +1,6 @@ + +Результат: `123 456`. + +Ленивый `\d+?` будет брать цифры до пробела, то есть `123`. После каждой цифры он будет останавливаться, проверять -- не пробел ли дальше? Если нет -- брать ещё цифру, в итоге возьмёт `123`. + +Затем в дело вступит `\d+`, который по-максимуму возьмёт дальнейшие цифры, то есть `456`. \ No newline at end of file diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/1-lazy-greedy/task.md b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/1-lazy-greedy/task.md new file mode 100644 index 00000000..f8889ea4 --- /dev/null +++ b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/1-lazy-greedy/task.md @@ -0,0 +1,8 @@ +# Совпадение для /d+? d+/ + +Что будет при таком поиске, когда сначала стоит ленивый, а потом жадный квантификаторы? + +```js +"123 456".match(/\d+? \d+/g) ); // какой результат? +``` + diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/2-difference-find-quote/solution.md b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/2-difference-find-quote/solution.md new file mode 100644 index 00000000..e8b0ee73 --- /dev/null +++ b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/2-difference-find-quote/solution.md @@ -0,0 +1,15 @@ +Они очень похожи и, да, *почти* одинаковы. Оба ищут от одной кавычки до другой. + +Различие здесь в символе точка `pattern:'.'`. Как мы помним, точка `pattern:'.'` обозначает *любой символ, кроме перевода строки*. + +А `pattern:[^"]` -- это *любой символ, кроме кавычки `pattern:'"'`. + +Получается, что первый регэксп `pattern:"[^"]*"` найдёт закавыченные строки с `\n` внутри, а второй регэксп `pattern:".*?"` -- нет. + +Вот пример: +```js run +alert( '"многострочный \n текст"'.match(/"[^"]*"/) ); // найдёт + +alert( '"многострочный \n текст"'.match(/".*?"/) ); // null (нет совпадений) +``` + diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/2-difference-find-quote/task.md b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/2-difference-find-quote/task.md new file mode 100644 index 00000000..fb9c168e --- /dev/null +++ b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/2-difference-find-quote/task.md @@ -0,0 +1,5 @@ +# Различие между "[^"]*" и ".*?" + +Регулярные выражения `pattern:"[^"]*"` и `pattern:".*?"` -- при выполнении одинаковы? + +Иначе говоря, существует ли такая строка, на которой они дадут разные результаты? Если да -- дайте такую строку. diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/3-find-html-comments/solution.md b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/3-find-html-comments/solution.md new file mode 100644 index 00000000..b5c3b9e3 --- /dev/null +++ b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/3-find-html-comments/solution.md @@ -0,0 +1,17 @@ +Нужно найти начало комментария `match:`. + +С первого взгляда кажется, что это сделает регулярное выражение `pattern:` -- квантификатор сделан ленивым, чтобы остановился, достигнув `match:-->`. + +Однако, точка в JavaScript -- любой символ, *кроме* конца строки. Поэтому такой регэксп не найдёт многострочный комментарий. + +Всё получится, если вместо точки использовать полностю "всеядный" `pattern:[\s\S]`. + +Итого: + +```js run +var re = //g; + +var str = '.. .. .. '; + +alert( str.match(re) ); // '', '' +``` diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/3-find-html-comments/task.md b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/3-find-html-comments/task.md new file mode 100644 index 00000000..f14f839b --- /dev/null +++ b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/3-find-html-comments/task.md @@ -0,0 +1,12 @@ +# Найти HTML-комментарии + +Найдите все HTML-комментарии в тексте: + +```js +var re = ..ваш регэксп.. + +var str = '.. .. .. '; + +alert( str.match(re) ); // '', '' +``` + diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md new file mode 100644 index 00000000..bd0934dd --- /dev/null +++ b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md @@ -0,0 +1,36 @@ +Начнём поиск с `pattern:<`, затем один или более произвольный символ, но до закрывающего "уголка": `pattern:.+?>`. + +Проверим, как работает этот регэксп: + +```js run +var re = /<.+?>/g; + +var str = '<> '; + +alert( str.match(re) ); // <> , , +``` + +Результат неверен! В качестве первого тега регэксп нашёл подстроку `match:<> `, но это явно не тег. + +Всё потому, что `pattern:.+?` -- это "любой символ (кроме `\n`), повторяющийся один и более раз до того, как оставшаяся часть шаблона совпадёт (ленивость)". + +Поэтому он находит первый `<`, затем есть "всё подряд" до следующего `>`. + +Первое совпадение получается как раз таким: + +``` +<.............> +<> +``` + +Правильным решением будет использовать `pattern:<[^>]+>`: + +```js run +var re = /<[^>]+>/g + +var str = '<> '; + +alert( str.match(re) ); // , , +``` + +Это же решение автоматически позволяет находится внутри тегу символу `\n`, который в класс точка `.` не входит. \ No newline at end of file diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md new file mode 100644 index 00000000..e0f4ce4f --- /dev/null +++ b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md @@ -0,0 +1,17 @@ +# Найти HTML-теги + +Создайте регулярное выражение для поиска всех (открывающихся и закрывающихся) HTML-тегов вместе с атрибутами. + +Пример использования: +```js run +var re = /* ваш регэксп */ + +var str = '<> '; + +alert( str.match(re) ); // '', '', '' +``` + +В этой задаче можно считать, что тег начинается с <, заканчивается > и может содержать внутри любые символы, кроме < и >. + +Но хотя бы один символ внутри тега должен быть: <> -- не тег. + diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/article.md b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/article.md new file mode 100644 index 00000000..db3f0a47 --- /dev/null +++ b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/article.md @@ -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-й позиции: + + ![](witch_greedy1.png) + +2. Кавычка найдена, далее движок проверяет, есть ли соответствие для остальной части паттерна. + + В данном случае следующий символ шаблона: `pattern:.` (точка). Она обозначает "любой символ", так что следующая буква строки `match:'w'` вполне подходит: + + ![](witch_greedy2.png) + +3. Далее "любой символ" повторяется, так как стоит квантификатор `pattern:.+`. Движок регулярных выражений берёт один символ за другим, до тех пор, пока у него это получается. + + В данном случае это означает "до конца строки": + + ![](witch_greedy3.png) + +4. Итак, текст закончился, движок регулярных выражений больше не может найти "любой символ", он закончил повторения для `pattern:.+` и переходит к следующему символу шаблона. + + Следующий символ шаблона -- это кавычка. Её тоже необходимо найти, чтобы соответствие было полным. А тут -- беда, ведь поисковый текст завершился! + + Движок регулярных выражений понимает, что, наверное, взял многовато `pattern:.+` и начинает отступать обратно. + + Иными словами, он сокращает текущее совпадение на один символ: + + ![](witch_greedy4.png) + + Это называется "фаза возврата" или "фаза бэктрекинга" (backtracking -- англ.). + + Теперь `pattern:.+` соответствует почти вся оставшаяся строка, за исключением одного символа, и движок регулярных выражений ещё раз пытается подобрать соответствие для остатка шаблона, начиная с оставшейся части строки. + + Если бы последним символом строки была кавычка `pattern:'"'`, то на этом бы всё и закончилось. Но последний символ `subject:'e'`, так что совпадения нет. + +5. ...Поэтому движок уменьшает число повторений `pattern:.+` ещё на один символ: + + ![](witch_greedy5.png) + + Кавычка `pattern:'"'` не совпадает с `subject:'n'`. Опять неудача. + +6. Движок продолжает отступать, он уменьшает количество повторений точки `pattern:'.'` до тех пор, пока остаток паттерна, то есть в данном случае кавычка `pattern:'"'`, не совпадёт: + + ![](witch_greedy6.png) + +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-й позиции: + + ![](witch_greedy1.png) + +2. Второй шаг -- тот же, находим произвольный символ `pattern:'.'`: + + ![](witch_greedy2.png) + +3. А вот дальше -- так как стоит ленивый режим работы `+`, то движок не повторит точку (произвольный символ) ещё раз, а останавливается на достигнутом и пытается проверить, есть ли соответствие остальной части шаблона, то есть `pattern:'"'`: + + ![](witch_lazy3.png) + + Если бы остальная часть шаблона на данной позиции совпала, то совпадение было бы найдено. Но в данном случае -- нет, символ `'i'` не равен '"'. +4. Движок регулярных выражений увиличивает количество повторений точки на одно и пытается найти соответствие остатку шаблона ещё раз: + + ![](witch_lazy4.png) +Опять неудача. Тогда поисковой движок увеличивает количество повторений ещё и ещё... +5. Только на пятом шаге поисковой движок наконец находит соответствие для остатка паттерна: + + ![](witch_lazy5.png) +6. Так как поиск происходит с флагом `g`, то он продолжается с конца текущего совпадения, давая ещё один результат: + + ![](witch_lazy6.png) + +В примере выше продемонстрирована работа ленивого режима для `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:"`. + +**Эта логика ни в коей мере не заменяет ленивые квантификаторы!** + +Она просто другая. И то и другое бывает полезно. + +Давайте посмотрим пример, когда нужен именно такой вариант, а ленивые квантификаторы не подойдут. + +Например, мы хотим найти в тексте ссылки вида ``, с любым содержанием `href`. + +Какое регулярное выражение для этого подойдёт? + +Первый вариант может выглядеть так: `pattern://g`. + +Проверим его: +```js run +var str = '......'; +var reg = //g; + +// Сработало! +alert( str.match(reg) ); // +``` + +А если в тексте несколько ссылок? + +```js run +var str = '...... ...'; +var reg = //g; + +// Упс! Сразу две ссылки! +alert( str.match(reg) ); // ... +``` + +На этот раз результат неверен. + +Жадный `pattern:.*` взял слишком много символов. + +Соответствие получилось таким: +``` + +... +``` + +Модифицируем шаблон -- добавим ленивость квантификатору `pattern:.*?`: + +```js run +var str = '...... ...'; +var reg = //g; + +// Сработало! +alert( str.match(reg) ); // , +``` + +Теперь всё верно, два результата: + +``` + +... +``` + +Почему теперь всё в порядке -- для внимательного читателя, после объяснений, данных выше в этой главе, должно быть полностью очевидно. + +Поэтому не будем останавливаться здесь на деталях, а попробуем ещё пример: + +```js run +var str = '......

...'; +var reg = //g; + +// Неправильное совпадение! +alert( str.match(reg) ); // ...

+``` + +Совпадение -- не ссылка, а более длинный текст. + +Получилось следующее: + +1. Найдено совпадение `match:`. + + В данном случае этот поиск закончится уже за пределами ссылки, в теге `

`, вообще не имеющем отношения к ``. +3. Получившееся совпадение: + + ``` + + ...

+ ``` + +Итак, ленивость нам не помогла. + +Необходимо как-то прекратить поиск `pattern:.*`, чтобы он не вышел за пределы кавычек. + +Для этого мы используем более точное указание, какие символы нам подходят, а какие нет. + +Правильный вариант: `pattern:[^"]*`. Этот шаблон будет брать все символы до ближайшей кавычки, как раз то, что требуется. + +Рабочий пример: + +```js run +var str1 = '......

...'; +var str2 = '...... ...'; +var reg = //g; + +// Работает! +alert( str1.match(reg) ); // null, совпадений нет, и это верно +alert( str2.match(reg) ); // , +``` + +## Итого + +Квантификаторы имеют два режима работы: + +Жадный +: Режим по умолчанию -- движок регулярных выражений повторяет его по-максимуму. Когда повторять уже нельзя, например нет больше цифр для `\d+`, он продолжает поиск с оставшейся части текста. Если совпадение найти не удалось -- отступает обратно, уменьшая количество повторений. + +Ленивый +: При указании после квантификатора символа `?` он работает в ленивом режиме. То есть, он перед каждым повторением проверяет совпадение оставшейся части шаблона на текущей позиции. + +Как мы видели в примере выше, ленивый режим -- не панацея от "слишком жадного" забора символов. Альтернатива -- более аккуратно настроенный "жадный", с исключением символов. Как мы увидим далее, можно исключать не только символы, но и целые подшаблоны. + diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy1.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy1.png new file mode 100644 index 00000000..5db6f17a Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy1.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy1@2x.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy1@2x.png new file mode 100644 index 00000000..966e27aa Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy1@2x.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy2.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy2.png new file mode 100644 index 00000000..9fdbe234 Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy2.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy2@2x.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy2@2x.png new file mode 100644 index 00000000..91e66582 Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy2@2x.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy3.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy3.png new file mode 100644 index 00000000..aeef75bd Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy3.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy3@2x.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy3@2x.png new file mode 100644 index 00000000..a8c62e60 Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy3@2x.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy4.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy4.png new file mode 100644 index 00000000..c18ab43e Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy4.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy4@2x.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy4@2x.png new file mode 100644 index 00000000..823a3717 Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy4@2x.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy5.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy5.png new file mode 100644 index 00000000..12442d2b Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy5.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy5@2x.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy5@2x.png new file mode 100644 index 00000000..29d3cf0a Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy5@2x.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy6.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy6.png new file mode 100644 index 00000000..3ed72b25 Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy6.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy6@2x.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy6@2x.png new file mode 100644 index 00000000..4833e676 Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_greedy6@2x.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy3.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy3.png new file mode 100644 index 00000000..1e0ce417 Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy3.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy3@2x.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy3@2x.png new file mode 100644 index 00000000..1e378468 Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy3@2x.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy4.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy4.png new file mode 100644 index 00000000..478834ee Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy4.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy4@2x.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy4@2x.png new file mode 100644 index 00000000..495a2f24 Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy4@2x.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy5.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy5.png new file mode 100644 index 00000000..e5054367 Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy5.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy5@2x.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy5@2x.png new file mode 100644 index 00000000..870d4633 Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy5@2x.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy6.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy6.png new file mode 100644 index 00000000..66728501 Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy6.png differ diff --git a/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy6@2x.png b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy6@2x.png new file mode 100644 index 00000000..4228e193 Binary files /dev/null and b/10-regular-expressions-javascript/07-regexp-greedy-and-lazy/witch_lazy6@2x.png differ diff --git a/10-regular-expressions-javascript/08-regexp-groups/1-find-webcolor-3-or-6/solution.md b/10-regular-expressions-javascript/08-regexp-groups/1-find-webcolor-3-or-6/solution.md new file mode 100644 index 00000000..e6af9289 --- /dev/null +++ b/10-regular-expressions-javascript/08-regexp-groups/1-find-webcolor-3-or-6/solution.md @@ -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 +``` + diff --git a/10-regular-expressions-javascript/08-regexp-groups/1-find-webcolor-3-or-6/task.md b/10-regular-expressions-javascript/08-regexp-groups/1-find-webcolor-3-or-6/task.md new file mode 100644 index 00000000..419c0476 --- /dev/null +++ b/10-regular-expressions-javascript/08-regexp-groups/1-find-webcolor-3-or-6/task.md @@ -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`, не должны подходить под регэксп. \ No newline at end of file diff --git a/10-regular-expressions-javascript/08-regexp-groups/2-parse-expression/solution.md b/10-regular-expressions-javascript/08-regexp-groups/2-parse-expression/solution.md new file mode 100644 index 00000000..d224acdc --- /dev/null +++ b/10-regular-expressions-javascript/08-regexp-groups/2-parse-expression/solution.md @@ -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 +``` + diff --git a/10-regular-expressions-javascript/08-regexp-groups/2-parse-expression/task.md b/10-regular-expressions-javascript/08-regexp-groups/2-parse-expression/task.md new file mode 100644 index 00000000..56ad74bb --- /dev/null +++ b/10-regular-expressions-javascript/08-regexp-groups/2-parse-expression/task.md @@ -0,0 +1,19 @@ +# Разобрать выражение + +Арифметическое выражение состоит из двух чисел и операции между ними, например: + +- `1 + 2` +- `1.2 * 3.4` +- `-3 / -6` +- `-2 - 2` + +Список операций: `"+"`, `"-"`, `"*"` и `"/"`. + +Также могут присутствовать пробелы вокруг оператора и чисел. + +Напишите функцию, которая будет получать выражение и возвращать массив из трёх аргументов: + +1. Первое число. +2. Оператор. +3. Второе число. + diff --git a/10-regular-expressions-javascript/08-regexp-groups/article.md b/10-regular-expressions-javascript/08-regexp-groups/article.md new file mode 100644 index 00000000..d8ab6ad6 --- /dev/null +++ b/10-regular-expressions-javascript/08-regexp-groups/article.md @@ -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 = '

Привет, мир!

'; +var reg = /<(.*?)>/; + +alert( str.match(reg) ); // массив:

, h1 +``` + +Заметим, что метод [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) выдаёт скобочные группы только при поиске без флага `/.../g`. В примере выше он нашёл только первое совпадение `match:

`, а закрывающий `match:

` не нашёл, поскольку без флага `/.../g` ищется только первое совпадение. + +Для того, чтобы искать и с флагом `/.../g` и со скобочными группами, используется метод [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec): + +```js run +var str = '

Привет, мир!

'; +var reg = /<(.*?)>/g; + +var match; + +while ((match = reg.exec(str)) !== null) { + // сначала выведет первое совпадение:

,h1 + // затем выведет второе совпадение:

,/h1 + alert(match); +} +``` + +Теперь найдено оба совпадения `pattern:<(.*?)>`, каждое -- массив из полного совпадения и скобочных групп (одна в данном случае). + +## Вложенные группы +Скобки могут быть и вложенными. В этом случае нумерация также идёт слева направо. + +Например, при поиске тега в `subject:` нас может интересовать: + +1. Содержимое тега целиком: `span class="my"`. +2. В отдельную переменную для удобства хотелось бы поместить тег: `span`. +3. Также может быть удобно отдельно выделить атрибуты `class="my"`. + +Добавим скобки в регулярное выражение: + +```js run +var str = ''; + +var reg = /<(([a-z]+)\s*([^>]*))>/; + +alert( str.match(reg) ); // , span class="my", span, class="my" +``` + +Вот так выглядят скобочные группы: + +![](regexp-nested-groups.png) + +На нулевом месте -- всегда совпадение полностью, далее -- группы. Нумерация всегда идёт слева направо, по открывающей скобке. + +В данном случае получилось, что группа 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+)`. Это удобно в тех случаях, когда содержимое скобок нас не интересует. diff --git a/10-regular-expressions-javascript/08-regexp-groups/regexp-nested-groups.png b/10-regular-expressions-javascript/08-regexp-groups/regexp-nested-groups.png new file mode 100644 index 00000000..4434b7be Binary files /dev/null and b/10-regular-expressions-javascript/08-regexp-groups/regexp-nested-groups.png differ diff --git a/10-regular-expressions-javascript/08-regexp-groups/regexp-nested-groups@2x.png b/10-regular-expressions-javascript/08-regexp-groups/regexp-nested-groups@2x.png new file mode 100644 index 00000000..2721e79d Binary files /dev/null and b/10-regular-expressions-javascript/08-regexp-groups/regexp-nested-groups@2x.png differ diff --git a/10-regular-expressions-javascript/09-regexp-backreferences/1-find-matching-bbtags/solution.md b/10-regular-expressions-javascript/09-regexp-backreferences/1-find-matching-bbtags/solution.md new file mode 100644 index 00000000..31f24848 --- /dev/null +++ b/10-regular-expressions-javascript/09-regexp-backreferences/1-find-matching-bbtags/solution.md @@ -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\]`. diff --git a/10-regular-expressions-javascript/09-regexp-backreferences/1-find-matching-bbtags/task.md b/10-regular-expressions-javascript/09-regexp-backreferences/1-find-matching-bbtags/task.md new file mode 100644 index 00000000..8596bed5 --- /dev/null +++ b/10-regular-expressions-javascript/09-regexp-backreferences/1-find-matching-bbtags/task.md @@ -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] +``` + diff --git a/10-regular-expressions-javascript/09-regexp-backreferences/article.md b/10-regular-expressions-javascript/09-regexp-backreferences/article.md new file mode 100644 index 00000000..25aefb97 --- /dev/null +++ b/10-regular-expressions-javascript/09-regexp-backreferences/article.md @@ -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`. +- Чтобы в принципе иметь возможность обратиться к скобочной группе -- не важно откуда, она не должна быть исключена из запоминаемых при помощи `?:`. Скобочные группы вида `(?:...)` не участвуют в нумерации. + diff --git a/10-regular-expressions-javascript/10-regexp-alternation/1-find-programming-language/solution.md b/10-regular-expressions-javascript/10-regexp-alternation/1-find-programming-language/solution.md new file mode 100644 index 00000000..289ba6d0 --- /dev/null +++ b/10-regular-expressions-javascript/10-regexp-alternation/1-find-programming-language/solution.md @@ -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++ +``` + diff --git a/10-regular-expressions-javascript/10-regexp-alternation/1-find-programming-language/task.md b/10-regular-expressions-javascript/10-regexp-alternation/1-find-programming-language/task.md new file mode 100644 index 00000000..b93570f3 --- /dev/null +++ b/10-regular-expressions-javascript/10-regexp-alternation/1-find-programming-language/task.md @@ -0,0 +1,6 @@ +# Найдите языки программирования + +Существует много языков программирования, например Java, JavaScript, PHP, C, C++. + +Напишите регулярное выражение, которое найдёт их все в строке "Java JavaScript PHP C++ C" + diff --git a/10-regular-expressions-javascript/10-regexp-alternation/2-match-quoted-string/solution.md b/10-regular-expressions-javascript/10-regexp-alternation/2-match-quoted-string/solution.md new file mode 100644 index 00000000..ece03804 --- /dev/null +++ b/10-regular-expressions-javascript/10-regexp-alternation/2-match-quoted-string/solution.md @@ -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\\" +``` diff --git a/10-regular-expressions-javascript/10-regexp-alternation/2-match-quoted-string/task.md b/10-regular-expressions-javascript/10-regexp-alternation/2-match-quoted-string/task.md new file mode 100644 index 00000000..2bde0073 --- /dev/null +++ b/10-regular-expressions-javascript/10-regexp-alternation/2-match-quoted-string/task.md @@ -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\\" .. +``` + diff --git a/10-regular-expressions-javascript/10-regexp-alternation/3-match-exact-tag/solution.md b/10-regular-expressions-javascript/10-regexp-alternation/3-match-exact-tag/solution.md new file mode 100644 index 00000000..e8895fdd --- /dev/null +++ b/10-regular-expressions-javascript/10-regexp-alternation/3-match-exact-tag/solution.md @@ -0,0 +1,17 @@ + +Начало шаблона очевидно: `pattern:`, так как `match:` удовлетворяет этому регэкспу. + +Нужно уточнить его. После `match:|\s.*?>)`. + +В действии: + +```js run +var re = /|\s.*?>)/g; + +alert( " - - - - - - Калькулятор процентов, из расчёта 12% годовых. -
- - - - - - - - - - - - - -
Сумма - -
Срок в месяцах - -
С капитализацией - -
- - -
- - - - - - - - - - - - - -
Было:Станет:
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/2-ui/4-forms-controls/3-events-change/1-calculate-capitalization/source.view/index.html b/2-ui/4-forms-controls/3-events-change/1-calculate-capitalization/source.view/index.html deleted file mode 100644 index b31bdf4b..00000000 --- a/2-ui/4-forms-controls/3-events-change/1-calculate-capitalization/source.view/index.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - Калькулятор процентов, из расчёта 12% годовых. -
- - - - - - - - - - - - - -
Сумма - -
Срок в месяцах - -
С капитализацией - -
- - -
- - - - - - - - - - - - - - - -
Было:Станет:
-
-
-
-
- - - - - - - diff --git a/2-ui/4-forms-controls/3-events-change/1-calculate-capitalization/task.md b/2-ui/4-forms-controls/3-events-change/1-calculate-capitalization/task.md deleted file mode 100644 index c8cf706c..00000000 --- a/2-ui/4-forms-controls/3-events-change/1-calculate-capitalization/task.md +++ /dev/null @@ -1,19 +0,0 @@ -importance: 5 - ---- - -# Автовычисление процентов по вкладу - -Создайте интерфейс для автоматического вычисления процентов по вкладу. - -Ставка фиксирована: 12% годовых. При включённом поле "капитализация" -- проценты приплюсовываются к сумме вклада каждый месяц ([сложный процент](http://damoney.ru/finance/slozniy-procent.php)). - -Пример: - -[iframe src="solution" height="350" border="1"] - -Технические требования: - -- В поле с суммой должно быть нельзя ввести не-цифру. При этом пусть в нём работают специальные клавиши и сочетания Ctrl-X/Ctrl-V. -- Изменения в форме отражаются в результатах сразу. - diff --git a/2-ui/4-forms-controls/4-forms-submit/2-form-validation/solution.md b/2-ui/4-forms-controls/3-events-change/1-deposit-calculator/solution.md similarity index 100% rename from 2-ui/4-forms-controls/4-forms-submit/2-form-validation/solution.md rename to 2-ui/4-forms-controls/3-events-change/1-deposit-calculator/solution.md diff --git a/2-ui/4-forms-controls/3-events-change/1-deposit-calculator/solution.view/index.html b/2-ui/4-forms-controls/3-events-change/1-deposit-calculator/solution.view/index.html new file mode 100644 index 00000000..4850b2ca --- /dev/null +++ b/2-ui/4-forms-controls/3-events-change/1-deposit-calculator/solution.view/index.html @@ -0,0 +1,112 @@ + + + + + + + + + + + Deposit calculator. + +
+ + + + + + + + + + + + + +
Initial deposit + +
How many months? + +
Interest per year? + +
+ + +
+ + + + + + + + + + + + + +
Was:Becomes:
+
+
+
+
+ + + + + + diff --git a/2-ui/4-forms-controls/3-events-change/1-deposit-calculator/source.view/index.html b/2-ui/4-forms-controls/3-events-change/1-deposit-calculator/source.view/index.html new file mode 100644 index 00000000..7f464020 --- /dev/null +++ b/2-ui/4-forms-controls/3-events-change/1-deposit-calculator/source.view/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + Deposit calculator. + +
+ + + + + + + + + + + + + +
Initial deposit + +
How many months? + +
Interest per year? + +
+ + +
+ + + + + + + + + + + + + +
Was:Becomes:
+
+
+
+
+ + + + + + diff --git a/2-ui/4-forms-controls/3-events-change/1-deposit-calculator/task.md b/2-ui/4-forms-controls/3-events-change/1-deposit-calculator/task.md new file mode 100644 index 00000000..e324577a --- /dev/null +++ b/2-ui/4-forms-controls/3-events-change/1-deposit-calculator/task.md @@ -0,0 +1,21 @@ +importance: 5 + +--- + +# Deposit calculator + +Create an interface that allows to enter a sum of bank deposit and percentage, then calculates how much it will be after given periods of time. + +Here's the demo: + +[iframe src="solution" height="350" border="1"] + +Any input change should be processed immediately. + +The formula is: +```js +// initial: the initial money sum +// interest: e.g. 0.05 means 5% per year +// years: how many years to wait +let result = Math.round(initial * (1 + interest * years)); +``` diff --git a/2-ui/4-forms-controls/3-events-change/article.md b/2-ui/4-forms-controls/3-events-change/article.md index c8b775ad..3a2343ef 100644 --- a/2-ui/4-forms-controls/3-events-change/article.md +++ b/2-ui/4-forms-controls/3-events-change/article.md @@ -1,185 +1,79 @@ -# Изменение: change, input, cut, copy, paste +# Handling change, input, cut, copy, paste -На элементах формы происходят события клавиатуры и мыши, но есть и несколько других, особенных событий. +Let's discuss various events that accompany data updates. -## Событие change +## Event: change -Событие [change](http://www.w3.org/TR/html5/forms.html#event-input-change) происходит по окончании изменении значения элемента формы, когда это изменение зафиксировано. +The [change](http://www.w3.org/TR/html5/forms.html#event-input-change) event triggers when the element has finished changing. -Для текстовых элементов это означает, что событие произойдёт не при каждом вводе, а при потере фокуса. +For text inputs that means that the event occurs when it looses focus. -Например, пока вы набираете что-то в текстовом поле ниже -- события нет. Но как только вы уведёте фокус на другой элемент, например, нажмёте кнопку -- произойдет событие `onchange`. +For instance, while we are typing in the text field below -- there's no event. But when we move the focus somewhere else, for instance, click on a button -- there will be a `change` event: -```html autorun height=40 +```html autorun height=40 run - + ``` -Для остальных же элементов: `select`, `input type=checkbox/radio` оно срабатывает сразу при выборе значения. +For other elements: `select`, `input type=checkbox/radio` it triggers right after the selection changes. -```warn header="Поздний `onchange` в IE8-" -В IE8- `checkbox/radio` при изменении мышью не инициируют событие сразу, а ждут потери фокуса. +## Event: input -Для того, чтобы видеть изменения `checkbox/radio` тут же -- в IE8- нужно повесить обработчик на событие `click` (оно произойдет и при изменении значения с клавиатуры) или воспользоваться событием `propertychange`, описанным далее. -``` +The `input` event triggers every time a value is modified. -## Событие input +For instance: -Событие `input` срабатывает *тут же* при изменении значения текстового элемента и поддерживается всеми браузерами, кроме IE8-. - -В IE9 оно поддерживается частично, а именно -- *не возникает при удалении символов* (как и `onpropertychange`). - -Пример использования (не работает в IE8-): - -```html autorun height=40 - oninput: +```html autorun height=40 run + oninput: ``` -В современных браузерах `oninput` -- самое главное событие для работы с элементом формы. Именно его, а не `keydown/keypress` следует использовать. +If we want to handle every modification of an `` then this event is the best choice. -Если бы ещё не проблемы со старыми IE... Впрочем, их можно решить при помощи события `propertychange`. +Unlike keyboard events it works on any value change, even those that does not involve keyboard actions: pasting with a mouse or using speech recognition to dictate the text. -## IE10-, событие propertychange +```smart header="Can't prevent anything in `oninput`" +The `input` event occurs after the value is modified. -Это событие происходит только в IE10-, при любом изменении свойства. Оно позволяет отлавливать изменение тут же. Оно нестандартное, и его основная область использования -- исправление недочётов обработки событий в старых IE. - -Если поставить его на `checkbox` в IE8-, то получится "правильное" событие `change`: - -```html autorun height=40 - Чекбокс с "onchange", работающим везде одинаково - +So we can't use `event.preventDefault()` there -- it's just too late, there would be no effect. ``` -Это событие также срабатывает при изменении значения текстового элемента. Поэтому его можно использовать в старых IE вместо `oninput`. +## Events: cut, copy, paste -К сожалению, в IE9 у него недочёт: оно не срабатывает при удалении символов. Поэтому сочетания `onpropertychange` + `oninput` недостаточно, чтобы поймать любое изменение поля в старых IE. Далее мы рассмотрим пример, как это можно сделать иначе. +These events occur on cutting/copying/pasting a value. -## События cut, copy, paste +They belong to [ClipboardEvent](https://www.w3.org/TR/clipboard-apis/#clipboard-event-interfaces) class and provide access to the data that is copied/pasted. -Эти события используются редко. Они происходят при вырезании/вставке/копировании значения. +We also can use `event.preventDefault()` to abort the action. -К сожалению, кросс-браузерного способа получить данные, которые вставляются/копируются, не существует, поэтому их основное применение -- это отмена соответствующей операции. +For instance, the code below prevents all such events and shows what we are trying to cut/copy/paste: -Например, вот так: - -```html autorun height=40 - event: +```html autorun height=40 run + ``` -## Пример: поле с контролем СМС +Technically, we can copy/paste everything. For instance, we can copy and file in the OS file manager, and paste it. -Как видим, событий несколько и они взаимно дополняют друг друга. +There's a list of methods [in the specification](https://www.w3.org/TR/clipboard-apis/#dfn-datatransfer) to work with different data types, read/write to the clipboard. -Посмотрим, как их использовать, на примере. +But please note that clipboard is a "global" OS-level thing. Most browsers allow read/write access to the clipboard only in the scope of certain user actions for the safety. Also it is forbidden to create "custom" clipboard events in all browsers except Firefox. -Сделаем поле для СМС, рядом с которым должно показываться число символов, обновляющееся при каждом изменении поля. +## Summary -Как такое реализовать? +Data change events: -Событие `input` идеально решит задачу во всех браузерах, кроме IE9-. Собственно, если IE9- нам не нужен, то на этом можно и остановиться. - -### IE9- - -В IE8- событие `input` не поддерживается, но, как мы видели ранее, есть `onpropertychange`, которое может заменить его. - -Что же касается IE9 -- там поддерживаются и `input` и `onpropertychange`, но они оба не работают при удалении символов. Поэтому мы будем отслеживать удаление при помощи `keyup` на `key:Delete` и `key:BackSpace` . А вот удаление командой "вырезать" из меню -- сможет отловить лишь `oncut`. - -Получается вот такая комбинация: - -```html autorun run height=60 - символов: - -``` - -Здесь мы добавили вызов `showCount` на все события, которые могут приводить к изменению значения. Да, иногда изменение будет обрабатываться несколько раз, но зато с гарантией. А лишние вызовы легко убрать, например, при помощи `throttle`-декоратора, описанного в задаче . - -**Есть и совсем другой простой, но действенный вариант: через `setInterval` регулярно проверять значение и, если оно слишком длинное, обрезать его.** - -Чтобы сэкономить ресурсы браузера, мы можем начинать отслеживание по `onfocus`, а прекращать -- по `onblur`, вот так: - -```html autorun height=60 - символов: - - -``` - -Обратим внимание -- весь этот "танец с бубном" нужен только для поддержки IE8-, в которых не поддерживается `oninput` и IE9, где `oninput` не работает при удалении. - -## Итого - -События изменения данных: - -| Событие | Описание | Особенности | +| Event | Description | Specials | |---------|----------|-------------| -| `change`| Изменение значения любого элемента формы. Для текстовых элементов срабатывает при потере фокуса. | В IE8- на чекбоксах ждет потери фокуса, поэтому для мгновенной реакции ставят также onclick-обработчик или onpropertychange. | -| `input` | Событие срабатывает только на текстовых элементах. Оно не ждет потери фокуса, в отличие от change. | В IE8- не поддерживается, в IE9 не работает при удалении символов. | -| `propertychange` | Только для IE10-. Универсальное событие для отслеживания изменения свойств элементов. Имя изменённого свойства содержится в `event.propertyName`. Используют для мгновенной реакции на изменение значения в старых IE. | В IE9 не срабатывает при удалении символов. | -| `cut/copy/paste` | Срабатывают при вставке/копировании/удалении текста. Если в их обработчиках отменить действие браузера, то вставки/копирования/удаления не произойдёт. | Вставляемое значение получить нельзя: на момент срабатывания события в элементе всё ещё *старое* значение, а новое недоступно. | - -Ещё особенность: в IE8- события `change`, `propertychange`, `cut` и аналогичные не всплывают. То есть, обработчики нужно назначать на сам элемент, без делегирования. - +| `change`| A value was changed. | For text inputs triggers on focus loss. | +| `input` | For text inputs on every change. | Triggers immediately unlike `change`. | +| `cut/copy/paste` | Cut/copy/paste actions. | The action can be prevented. The `event.clipbordData` property gives read/write access to the clipboard. | diff --git a/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/solution.md b/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/solution.md index 571ea27f..781ce5d5 100644 --- a/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/solution.md +++ b/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/solution.md @@ -1,8 +1,4 @@ -Модальное окно делается путём добавления к документу `DIV`, полностью перекрывающего документ и имеющего больший `z-index`. - -В результате все клики будут доставаться этому `DIV'у`: - -Стиль: +A modal window can be implemented using a half-transparent `
` that covers the whole window, like this: ```css #cover-div { @@ -17,5 +13,8 @@ } ``` -Самой форме можно дать еще больший `z-index`, чтобы она была над `DIV'ом`. Мы не помещаем форму в контейнер, чтобы она не унаследовала полупрозрачность. +Because the `
` covers everything, it gets all clicks, not the page below it. +Also we can prevent page scroll by setting `body.style.overflowY='hidden'`. + +The form should be not in the `
`, but next to it, because we don't want it to have `opacity`. diff --git a/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/solution.view/index.html b/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/solution.view/index.html index ef7cbfd0..ec9e1b5e 100644 --- a/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/solution.view/index.html +++ b/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/solution.view/index.html @@ -3,96 +3,49 @@ - + -

Нажмите на кнопку ниже

+

Click the button below

- +
- - + +
- - - \ No newline at end of file + diff --git a/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/solution.view/style.css b/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/solution.view/style.css new file mode 100644 index 00000000..d4da46ac --- /dev/null +++ b/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/solution.view/style.css @@ -0,0 +1,51 @@ +html, +body { + margin: 0; + padding: 0; + width: 100%; + height: 100%; +} + +#prompt-form { + display: inline-block; + padding: 5px 5px 5px 70px; + width: 200px; + border: 1px solid black; + background: white url(https://en.js.cx/clipart/prompt.png) no-repeat left 5px; + vertical-align: middle; +} + +#prompt-form-container { + position: fixed; + top: 0; + left: 0; + z-index: 9999; + display: none; + width: 100%; + height: 100%; + text-align: center; +} + +#prompt-form-container:before { + display: inline-block; + height: 100%; + content: ''; + vertical-align: middle; +} + +#cover-div { + position: fixed; + top: 0; + left: 0; + z-index: 9000; + width: 100%; + height: 100%; + background-color: gray; + opacity: 0.3; +} + +#prompt-form input[name="text"] { + display: block; + margin: 5px; + width: 180px; +} diff --git a/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/source.view/index.html b/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/source.view/index.html index 846f2706..230a5117 100644 --- a/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/source.view/index.html +++ b/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/source.view/index.html @@ -3,47 +3,7 @@ - + @@ -51,15 +11,15 @@
-
Введите, пожалуйста... -
Что-то..
+
Enter something... +
Please..
- - + +
- \ No newline at end of file + diff --git a/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/source.view/style.css b/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/source.view/style.css new file mode 100644 index 00000000..5b327593 --- /dev/null +++ b/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/source.view/style.css @@ -0,0 +1,39 @@ +html, +body { + width: 100%; + height: 100%; + padding: 0; + margin: 0; +} + +#prompt-form { + display: inline-block; + padding: 5px 5px 5px 70px; + width: 200px; + border: 1px solid black; + background: white url(https://en.js.cx/clipart/prompt.png) no-repeat left 5px; + vertical-align: middle; +} + +#prompt-form-container { + position: fixed; + top: 0; + left: 0; + z-index: 9999; + width: 100%; + height: 100%; + text-align: center; +} + +#prompt-form-container:before { + display: inline-block; + height: 100%; + content: ''; + vertical-align: middle; +} + +#prompt-form input[name="text"] { + display: block; + margin: 5px; + width: 180px; +} diff --git a/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/task.md b/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/task.md index 8b899ef6..c5b09589 100644 --- a/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/task.md +++ b/2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/task.md @@ -2,32 +2,32 @@ importance: 5 --- -# Модальное диалоговое окно +# Modal form -Создайте функцию `showPrompt(text, callback)`, которая выводит форму для ввода с сообщением `text` и кнопками `ОК/Отмена`. +Create a function `showPrompt(html, callback)` that shows a form with the message `html`, an input field and buttons `OK/CANCEL`. -- При отправке формы (OK/ввод в текстовом поле) -- должна вызываться функция `callback` со значением поля. -- При нажатии на `Отмена` или на клавишу `key:Esc` -- должна вызываться функция `callback(null)`. Клавиша `key:Esc` должна закрывать форму всегда, даже если поле для ввода сообщения не в фокусе. +- A user should type something into a text field and press `key:Enter` or the OK button, then `callback(value)` is called with the value he entered. +- Otherwise if the user presses `key:Esc` or CANCEL, then `callback(null)` is called. -Особенности реализации: +In both cases that ends the input process and removes the form. -- Форма должна показываться в центре окна (и оставаться в центре при изменении его размеров, а также при прокрутке окна!). -- Текст может состоять из нескольких строк, возможен любой HTML -- При показе формы остальные элементы страницы использовать нельзя, не работают другие кнопки и т.п, это окно -- *модальное*. -- При показе формы -- сразу фокус на `INPUT` для ввода. -- Нажатия `key:Tab`/`key:Shift+Tab` переключают в цикле только по полям формы, они не позволяют переключиться на другие элементы страницы. +Requirements: + +- The form should be in the center of the window. +- The form is *modal*. In other words, no interaction with the rest of the page is possible until the user closes it. +- When the form is shown, the focus should be inside the `` for the user. +- Keys `key:Tab`/`key:Shift+Tab` should shift the focus between form fields, don't allow it to leave for other page elements. Пример использования: ```js -showPrompt("Введите что-нибудь
... умное :)", function(value) { - alert( value ); +showPrompt("Enter something
...smart :)", function(value) { + alert(value); }); ``` -Демо в ифрейме: +A demo in the iframe: [iframe src="solution" height=160 border=1] -Исходный HTML/CSS для формы с готовым fixed-позиционированием - в песочнице. - +P.S. The source document has HTML/CSS for the form with fixed positioning, but it's up to you to make it modal. diff --git a/2-ui/4-forms-controls/4-forms-submit/2-form-validation/solution.view/index.html b/2-ui/4-forms-controls/4-forms-submit/2-form-validation/solution.view/index.html deleted file mode 100644 index b4c28716..00000000 --- a/2-ui/4-forms-controls/4-forms-submit/2-form-validation/solution.view/index.html +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - -
От кого - -
Ваш пароль - -
Повторите пароль - -
Куда - -
- - Сообщение: - - - -
- - - - - - - \ No newline at end of file diff --git a/2-ui/4-forms-controls/4-forms-submit/2-form-validation/source.view/index.html b/2-ui/4-forms-controls/4-forms-submit/2-form-validation/source.view/index.html deleted file mode 100644 index b316af7c..00000000 --- a/2-ui/4-forms-controls/4-forms-submit/2-form-validation/source.view/index.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - -
От кого - -
Ваш пароль - -
Повторите пароль - -
Куда - -
- - Сообщение: - - - -
- - - - - - - \ No newline at end of file diff --git a/2-ui/4-forms-controls/4-forms-submit/2-form-validation/task.md b/2-ui/4-forms-controls/4-forms-submit/2-form-validation/task.md deleted file mode 100644 index 42ec25fd..00000000 --- a/2-ui/4-forms-controls/4-forms-submit/2-form-validation/task.md +++ /dev/null @@ -1,17 +0,0 @@ -importance: 3 - ---- - -# Валидация формы - -Напишите функцию `validate(form)`, которая проверяет содержимое формы по клику на кнопку "Проверить". - -Ошибки: - -1. Одно из полей не заполнено. -2. Пароли не совпадают. - -Ошибка должна сопровождаться сообщением у поля. Например: - -[iframe height=280 src="solution"] - diff --git a/2-ui/4-forms-controls/4-forms-submit/article.md b/2-ui/4-forms-controls/4-forms-submit/article.md index f3eb1409..0b4b6e1b 100644 --- a/2-ui/4-forms-controls/4-forms-submit/article.md +++ b/2-ui/4-forms-controls/4-forms-submit/article.md @@ -1,56 +1,67 @@ -# Формы: отправка, событие и метод submit +# Form submission: event and method submit -Событие `submit` возникает при отправке формы. Наиболее частое его применение -- это *валидация* (проверка) формы перед отправкой. +The `submit` event triggers when the form is submitted, it is usually used to validate the form before sending it to the server or to abort the submission and process it in Javascript. -Метод `submit` позволяет инициировать отправку формы из JavaScript, без участия пользователя. Далее мы рассмотрим детали их использования. +The method `form.submit()` allows to initiate form sending from JavaScript. We can use it to dynamically create and send our own forms to server. + +Let's see more details of them. [cut] -## Событие submit +## Event: submit -Чтобы отправить форму на сервер, у посетителя есть два способа: +There are two main ways to submit a form: -1. **Первый -- это нажать кнопку `` или ``.** -2. **Второй -- нажать Enter, находясь на каком-нибудь поле.** +1. The first -- to click `` or ``. +2. The second -- press `key:Enter` on an input field. -Какой бы способ ни выбрал посетитель -- будет сгенерировано событие `submit`. Обработчик в нём может проверить данные и, если они неверны, то вывести ошибку и сделать `event.preventDefault()` -- тогда форма не отправится на сервер. +Both actions lead to `submit` event on the form. The handler can check the data, and if there are errors, show them and call `event.preventDefault()`, then the form won't be sent to the server. -Например, в таком HTML оба способа выведут `alert`, форма не будет отправлена: +In the form below: +1. Go into the text field and press `key:Enter`. +2. Click ``. -```html autorun height=80 no-beautify +Both actions show `alert` and the form is not sent anywhere due to `return false`: + +```html autorun height=60 no-beautify
- Первый: Enter в текстовом поле
- Второй: Нажать на "Отправить": + First: Enter in the input field
+ Second: Click "submit":
``` -Ожидаемое поведение: +````smart header="Relation between `submit` and `click`" +When a form is sent using `key:Enter` on an input field, a `click` event triggers on the ``. -1. Перейдите в текстовое поле и нажмите Enter, будет событие, но форма не отправится на сервер благодаря `return false` в обработчике. -2. То же самое произойдет при клике на ``. +That's rather funny, because there was no click at all. -````smart header="Взаимосвязь событий `submit` и `click`" -При отправке формы путём нажатия Enter на текстовом поле, на элементе `` везде, кроме IE8-, генерируется событие `click`. - -Это довольно забавно, учитывая что клика-то и не было. - -```html autorun height=80 -
- +Here's the demo: +```html autorun height=60 + +
``` + ```` -```warn header="В IE8- событие `submit` не всплывает" -В IE8- событие `submit` не всплывает. Нужно вешать обработчик `submit` на сам элемент формы, без использования делегирования. +## Method: submit + +To submit a form to the server manually, we can call `form.submit()`. + +Then the `submit` event is not generated. It is assumed that if the programmer calls `form.submit()`, then the script already did all related processing. + +Sometimes that's used to manually create and send a form, like this: + +```js run +let form = document.createElement('form'); +form.action = 'https://google.com/search'; +form.method = 'GET'; + +form.innerHTML = ''; + +// the form must be in the document to submit it +document.body.append(form); + +form.submit(); ``` - -## Метод submit - -Чтобы отправить форму на сервер из JavaScript -- нужно вызвать на элементе формы метод `form.submit()`. - -При этом само событие `submit` не генерируется. Предполагается, что если программист вызывает метод `form.submit()`, то он выполнил все проверки. - -Это используют, в частности, для искусственной генерации и отправки формы. - diff --git a/figures.sketch b/figures.sketch index 222695ab..d2a41958 100644 Binary files a/figures.sketch and b/figures.sketch differ