٢٥ مارس ٢٠٢١

التقاط المجموعات

يمكن وضع جزء من النموذج بين قوسين نمط: (...). وهذا ما يسمى “مجموعة أسر”.

هذا له تأثيران:

  1. يسمح بالحصول على جزء من المباراة كبند منفصل في مصفوفة النتائج.
  2. إذا وضعنا كمياً بعد الأقواس ، فإنه ينطبق على الأقواس ككل.

الأمثلة

دعونا نرى كيف تعمل الأقواس في الأمثلة.

مثال: gogogo

بدون قوسين ، فإن النمط النمط: go + يعني الموضوع: g ، متبوعًا بـالموضوع: o مكررًا مرة واحدة أو أكثر. على سبيل المثال ، goooo أو gooooooooo.

تجمع الأقواس الأحرف معًا ، لذا النمط: (go) + يعني go و gogo و gogogo وما إلى ذلك.

alert( 'Gogogo now!'.match(/(go)+/ig) ); // "Gogogo"

مثال: المجال

دعونا نجعل شيئًا أكثر تعقيدًا – تعبيرًا عاديًا للبحث عن نطاق موقع ويب.

فمثلا:

mail.com
users.mail.com
smith.users.mail.com

كما نرى ، المجال يتكون من كلمات متكررة ، نقطة بعد كل واحدة باستثناء الأخيرة.

في التعبيرات العادية هذا نمط: (\ w + \.) + \ w +:

let regexp = /(\w+\.)+\w+/g;

alert( "site.com my.site.com".match(regexp) ); // site.com,my.site.com

يعمل البحث ، ولكن لا يمكن أن يتطابق النمط مع النطاق بواصلة ، على سبيل المثال my-site.com ، لأن الواصلة لا تنتمي إلى النمطclass: \ w.

يمكننا إصلاحه عن طريق استبدال \ w بـ [\ w-]في كل كلمة باستثناء الكلمة الأخيرة: ([\ w -] + \.) + \ w +.

مثال: البريد الإلكتروني

يمكن توسيع المثال السابق. يمكننا إنشاء تعبير عادي للرسائل الإلكترونية بناءً عليه.

تنسيق البريد الإلكتروني هو: name @ domain. يمكن أن تكون أي كلمة الاسم والواصلات والنقاط مسموحًا بها. في التعبيرات العادية هذا النمط: [-. \ w] +.

النمط:

let regexp = /[-.\w]+@([\w-]+\.)+[\w-]+/g;

alert("my@mail.com @ his@site.com.uk".match(regexp)); // my@mail.com, his@site.com.uk

هذا التعبير العادي ليس مثاليًا ، ولكنه يعمل في الغالب ويساعد على إصلاح الأخطاء العرضية. لا يمكن إجراء الاختيار الوحيد الموثوق به حقًا للبريد الإلكتروني إلا عن طريق إرسال بريد إلكتروني.

أقواس المحتويات في المباراة

الأقواس مرقمة من اليسار إلى اليمين. يحفظ محرك البحث المحتوى المطابق لكل منها ويسمح بالحصول عليه في النتيجة.

الطريقة str.match (regexp) ، إذا لم يكن لـ regexp علامةg ، فابحث عن المطابقة الأولى وترجعها كمصفوفة:

  1. في الفهرس 0: المباراة الكاملة.
  2. في الفهرس 1: محتويات الأقواس الأولى.
  3. في الفهرس 2: محتويات الأقواس الثانية.
  4. … وهكذا …

على سبيل المثال ، نود العثور على علامات HTML <. *؟> ، ومعالجتها. سيكون من المناسب وجود محتوى علامة (ما يوجد داخل الزوايا) ، في متغير منفصل.

دعونا نلف المحتوى الداخلي بين قوسين ، مثل هذا: <(. *؟)>.

الآن سنحصل على كل من العلامة على أنها مطابقة كاملة: <h1> ومحتوياتها مطابقة: h1 في الصفيف الناتج:

let str = '<h1>Hello, world!</h1>';

let tag = str.match(/<(.*?)>/);

alert( tag[0] ); // <h1>
alert( tag[1] ); // h1

المجموعات المتداخلة

يمكن أن تتداخل الأقواس. في هذه الحالة ، ينتقل الترقيم أيضًا من اليسار إلى اليمين.

على سبيل المثال ، عند البحث عن علامة في الموضوع: <span class =" my "> قد نكون مهتمين بما يلي:

  1. محتوى العلامة ككل: span class =" my ".
  2. اسم العلامة: span.
  3. سمات العلامة: class =" my ".

دعونا نضيف أقواسًا لهم: <(([a-z] +) \ s * ([^>] *))>.

إليك كيفية ترقيمها (من اليسار إلى اليمين ، عن طريق قوس الافتتاح):

In action:

let str = '<span class="my">';

let regexp = /<(([a-z]+)\s*([^>]*))>/;

let result = str.match(regexp);
alert(result[0]); // <span class="my">
alert(result[1]); // span class="my"
alert(result[2]); // span
alert(result[3]); // class="my"

دائمًا ما يحمل الفهرس الصفري “النتيجة” المطابقة الكاملة.

ثم المجموعات ، مرقمة من اليسار إلى اليمين بواسطة قوس افتتاح. تم إرجاع المجموعة الأولى على أنها نتيجة [1]. هنا يرفق محتوى العلامة بالكامل.

ثم في النتيجة [2] تنتقل المجموعة من نمط `` الفتح الثاني ‘’: ([az] +) - اسم العلامة ، ثم في النتيجة [3] العلامة: النمط: ([^>] * ) `.

محتويات كل مجموعة في السلسلة:

المجموعات الاختيارية

حتى إذا كانت المجموعة اختيارية ولا وجود لها في المطابقة (على سبيل المثال ، تحتوي على النموذج المُحدِّد الكمي: (...)؟) ، فإن عنصر صفيف النتيجة المطابق موجود ويساويغير معرّف.

على سبيل المثال ، دعنا نفكر في regexp a (z)؟ (c)؟. تبحث عن “” “متبوعًا اختياريًا بـ” “z” “متبوعًا اختياريًا بـ” “c” ".

إذا قمنا بتشغيله على السلسلة بحرف واحد a ، فإن النتيجة هي:

let match = 'a'.match(/a(z)?(c)?/);

alert( match.length ); // 3
alert( match[0] ); // a (whole match)
alert( match[1] ); // undefined
alert( match[2] ); // undefined

الصفيف له طول 3 ، لكن كل المجموعات فارغة.

وإليك مطابقة أكثر تعقيدًا للسلسلة ac:

let match = 'ac'.match(/a(z)?(c)?/)

alert( match.length ); // 3
alert( match[0] ); // ac (whole match)
alert( match[1] ); // undefined, because there's nothing for (z)?
alert( match[2] ); // c

طول الصفيف دائم: 3. ولكن لا يوجد شيء للمجموعة 'pattern: (z)؟ ، لذا فإن النتيجة هي" ["ac"، undefined، "c"].

البحث عن جميع التطابقات مع المجموعات: matchAll

matchAll هي طريقة جديدة ، قد تكون هناك حاجة إلى تعبئة متعددة

الطريقة matchAll غير مدعومة في المتصفحات القديمة.

قد تكون هناك حاجة إلى تعبئة متعددة ، مثل https://github.com/ljharb/String.prototype.matchAll.

عندما نبحث عن جميع التطابقات (الإبلاغ عن g) ، لا تُرجع طريقةmatch محتويات المجموعات.

على سبيل المثال ، دعنا نجد كل العلامات في سلسلة:

let str = '<h1> <h2>';

let tags = str.match(/<(.*?)>/g);

alert( tags ); // <h1>,<h2>

والنتيجة هي مجموعة من التطابقات ، ولكن بدون تفاصيل حول كل منها. ولكن في الممارسة العملية ، نحتاج عادةً إلى محتويات مجموعات الالتقاط في النتيجة.

للحصول عليها ، يجب البحث باستخدام الطريقة str.matchAll (regexp).

تمت إضافتها إلى لغة جافا سكريبت بعد فترة طويلة من “التطابق” ، باعتبارها “نسختها الجديدة والمحسنة”.

تمامًا مثل match ، فإنه يبحث عن المباريات ، ولكن هناك 3 اختلافات:

  1. لا تقوم بإرجاع صفيف ، ولكن كائن قابل للتكرار.
  2. عند وجود العلامة “pattern: g” ، فإنها تُرجع كل مطابقة كمصفوفة بمجموعات.
  3. في حالة عدم وجود تطابقات ، فإنها لا تُرجع “قيمة خالية” ، بل تُرجع كائنًا فارغًا قابلًا للتكرار.

على سبيل المثال:

let results = '<h1> <h2>'.matchAll(/<(.*?)>/gi);

// results - is not an array, but an iterable object
alert(results); // [object RegExp String Iterator]

alert(results[0]); // undefined (*)

results = Array.from(results); // let's turn it into array

alert(results[0]); // <h1>,h1 (1st tag)
alert(results[1]); // <h2>,h2 (2nd tag)

كما نرى ، فإن الفرق الأول مهم للغاية ، كما هو موضح في السطر (*). لا يمكننا الحصول على المطابقة كـ "النتائج [0]، لأن هذا الكائن ليس كاذبًا. يمكننا تحويلها إلىArrayحقيقي باستخدام Array.from`. هناك المزيد من التفاصيل حول المصفوفات الكاذبة والقابلة للتكرار في المقالة <info: iterable>.

ليست هناك حاجة في Array.from إذا كنا نراجع النتائج:

let results = '<h1> <h2>'.matchAll(/<(.*?)>/gi);

for(let result of results) {
  alert(result);
  // first alert: <h1>,h1
  // second: <h2>,h2
}

… أو باستخدام الـ destructuring:

let [tag1, tag2] = '<h1> <h2>'.matchAll(/<(.*?)>/gi);

كل مطابقة ، يتم إرجاعها بواسطة matchAll ، لها نفس التنسيق الذي تم إرجاعه بواسطةمطابقة بدون نمط العلامة: g: إنها مصفوفة ذات خصائص إضافيةفهرس (فهرس المطابقة في السلسلة) و الإدخال (سلسلة المصدر ):

let results = '<h1> <h2>'.matchAll(/<(.*?)>/gi);

let [tag1, tag2] = results;

alert( tag1[0] ); // <h1>
alert( tag1[1] ); // h1
alert( tag1.index ); // 0
alert( tag1.input ); // <h1> <h2>
لماذا نتيجة

لماذا تم تصميم الطريقة بهذه الطريقة؟ والسبب بسيط – للتحسين.

استدعاء “matchAll” لا يجري البحث. بدلاً من ذلك ، تقوم بإرجاع كائن قابل للتكرار ، بدون النتائج في البداية. يتم إجراء البحث في كل مرة نكرر فيها ذلك ، على سبيل المثال في الحلقة.

لذلك ، سيتم العثور على العديد من النتائج حسب الحاجة ، وليس أكثر.

على سبيل المثال من المحتمل أن يكون هناك 100 تطابق في النص ، ولكن في حلقة for..of وجدنا 5 منها ، ثم قررنا أنها كافية وقمنا بعمل" break ".ثم لن يقضي المحرك وقتًا في العثور على 95 تشابه آخر.

المجموعات المسماة

من الصعب تذكر المجموعات بأرقامها. بالنسبة للأنماط البسيطة ، يمكن القيام بذلك ، ولكن بالنسبة للأنماط الأكثر تعقيدًا ، يعد حساب الأقواس غير مريح. لدينا خيار أفضل بكثير: إعطاء أسماء للأقواس.

يتم ذلك عن طريق وضع "pattern :؟ ` بعد علامة الافتتاح مباشرة.

على سبيل المثال ، دعنا نبحث عن تاريخ بتنسيق “عام-شهر-يوم”:

let dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;
let str = "2019-04-30";

let groups = str.match(dateRegexp).groups;

alert(groups.year); // 2019
alert(groups.month); // 04
alert(groups.day); // 30

كما ترى ، المجموعات موجودة في خاصية “.groups” للمباراة.

للبحث عن جميع التواريخ ، يمكننا إضافة العلم g.

سنحتاج أيضًا إلى “matchAll” للحصول على تطابقات كاملة ، جنبًا إلى جنب مع المجموعات:

let dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/g;

let str = "2019-10-30 2020-01-01";

let results = str.matchAll(dateRegexp);

for(let result of results) {
  let {year, month, day} = result.groups;

  alert(`${day}.${month}.${year}`);
  // first alert: 30.10.2019
  // second: 01.01.2020
}

الاستيلاء على المجموعات في الاستبدال

تسمح الطريقة str.replace (regexp ، الاستبدال) التي تستبدل جميع التطابقات بـ regexp فيstr باستخدام محتويات الأقواس في سلسلة replace. يتم ذلك باستخدام $ n ، حيث n هو رقم المجموعة.

فمثلا،

let str = "John Bull";
let regexp = /(\w+) (\w+)/;

alert( str.replace(regexp, '$2, $1') ); // Bull, John

بالنسبة للأقواس المسماة ، سيكون المرجع $ <name>.

على سبيل المثال ، دعنا نعيد تنسيق التواريخ من “year-month-day” إلى “day.month.year”:

let regexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/g;

let str = "2019-10-30, 2020-01-01";

alert( str.replace(regexp, '$<day>.$<month>.$<year>') );
// 30.10.2019, 01.01.2020

المجموعات غير الملتقطة مع؟:

في بعض الأحيان نحتاج إلى قوسين لتطبيق مُحدِّد الكمية بشكل صحيح ، لكننا لا نريد محتوياتها في النتائج.

يمكن استبعاد مجموعة بإضافة “pattern:؟:” في البداية.

على سبيل المثال ، إذا أردنا العثور على "pattern: (go) +، لكننا لا نريد محتويات الأقواس (go) كعنصر صفيف منفصل ، فيمكننا كتابة: pattern :( ؟: go) + ` .

في المثال أدناه ، نحصل فقط على الاسم John كعضو منفصل في المباراة:

let str = "Gogogo John!";

// ?: exludes 'go' from capturing
let regexp = /(?:go)+ (\w+)/i;

let result = str.match(regexp);

alert( result[0] ); // Gogogo John (full match)
alert( result[1] ); // John
alert( result.length ); // 2 (no more items in the array)

الملخص

تجمع الأقواس معًا جزءًا من التعبير العادي ، بحيث ينطبق المقياس عليه ككل.

يتم ترقيم مجموعات الأقواس من اليسار إلى اليمين ، ويمكن اختياريًا تسميتها بـ (؟ <name> ...).

يمكن الحصول على المحتوى المطابق لمجموعة ما في النتائج:

  • تُظهر الطريقة str.match مجموعات الالتقاط فقط بدون وضع علامة على" النموذج: g`.
  • الطريقة str.matchAll تُرجع دائمًا مجموعات الالتقاط.

إذا لم يكن للأقواس اسم ، فإن محتوياتها متاحة في مصفوفة المطابقة برقمها. الأقواس المسموعة متاحة أيضًا في خاصية “المجموعات”.

يمكننا أيضًا استخدام محتويات الأقواس في سلسلة الاستبدال في str.replace: بالرقم$ n أو بالاسم $ <name>.

يمكن استبعاد مجموعة من الترقيم عن طريق إضافة “pattern:؟:” في بدايتها. يُستخدم هذا عندما نحتاج إلى تطبيق مُحدِّد الكمية على المجموعة بأكملها ، ولكن لا نريدها كبند منفصل في صفيف النتائج. لا يمكننا أيضًا الإشارة إلى هذه الأقواس في سلسلة الاستبدال.

مهمه

MAC-address of a network interface consists of 6 two-digit hex numbers separated by a colon.

For instance: '01:32:54:67:89:AB'.

Write a regexp that checks whether a string is MAC-address.

Usage:

let regexp = /your regexp/;

alert( regexp.test('01:32:54:67:89:AB') ); // true

alert( regexp.test('0132546789AB') ); // false (no colons)

alert( regexp.test('01:32:54:67:89') ); // false (5 numbers, must be 6)

alert( regexp.test('01:32:54:67:89:ZZ') ) // false (ZZ at the end)

A two-digit hex number is [0-9a-f]{2} (assuming the flag i is set).

We need that number NN, and then :NN repeated 5 times (more numbers);

The regexp is: [0-9a-f]{2}(:[0-9a-f]{2}){5}

Now let’s show that the match should capture all the text: start at the beginning and end at the end. That’s done by wrapping the pattern in ^...$.

Finally:

let regexp = /^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$/i;

alert( regexp.test('01:32:54:67:89:AB') ); // true

alert( regexp.test('0132546789AB') ); // false (no colons)

alert( regexp.test('01:32:54:67:89') ); // false (5 numbers, need 6)

alert( regexp.test('01:32:54:67:89:ZZ') ) // false (ZZ in the end)

Write a RegExp that matches colors in the format #abc or #abcdef. That is: # followed by 3 or 6 hexadecimal digits.

Usage example:

let regexp = /your regexp/g;

let str = "color: #3f3; background-color: #AA00ef; and: #abcd";

alert( str.match(regexp) ); // #3f3 #AA00ef

P.S. This should be exactly 3 or 6 hex digits. Values with 4 digits, such as #abcd, should not match.

A regexp to search 3-digit color #abc: /#[a-f0-9]{3}/i.

We can add exactly 3 more optional hex digits. We don’t need more or less. The color has either 3 or 6 digits.

Let’s use the quantifier {1,2} for that: we’ll have /#([a-f0-9]{3}){1,2}/i.

Here the pattern [a-f0-9]{3} is enclosed in parentheses to apply the quantifier {1,2}.

In action:

let regexp = /#([a-f0-9]{3}){1,2}/gi;

let str = "color: #3f3; background-color: #AA00ef; and: #abcd";

alert( str.match(regexp) ); // #3f3 #AA00ef #abc

There’s a minor problem here: the pattern found #abc in #abcd. To prevent that we can add \b to the end:

let regexp = /#([a-f0-9]{3}){1,2}\b/gi;

let str = "color: #3f3; background-color: #AA00ef; and: #abcd";

alert( str.match(regexp) ); // #3f3 #AA00ef

Write a regexp that looks for all decimal numbers including integer ones, with the floating point and negative ones.

An example of use:

let regexp = /your regexp/g;

let str = "-1.5 0 2 -123.4.";

alert( str.match(regexp) ); // -1.5, 0, 2, -123.4

A positive number with an optional decimal part is: \d+(\.\d+)?.

Let’s add the optional - in the beginning:

let regexp = /-?\d+(\.\d+)?/g;

let str = "-1.5 0 2 -123.4.";

alert( str.match(regexp) );   // -1.5, 0, 2, -123.4

An arithmetical expression consists of 2 numbers and an operator between them, for instance:

  • 1 + 2
  • 1.2 * 3.4
  • -3 / -6
  • -2 - 2

The operator is one of: "+", "-", "*" or "/".

There may be extra spaces at the beginning, at the end or between the parts.

Create a function parse(expr) that takes an expression and returns an array of 3 items:

  1. The first number.
  2. The operator.
  3. The second number.

For example:

let [a, op, b] = parse("1.2 * 3.4");

alert(a); // 1.2
alert(op); // *
alert(b); // 3.4

A regexp for a number is: -?\d+(\.\d+)?. We created it in the previous task.

An operator is [-+*/]. The hyphen - goes first in the square brackets, because in the middle it would mean a character range, while we just want a character -.

The slash / should be escaped inside a JavaScript regexp /.../, we’ll do that later.

We need a number, an operator, and then another number. And optional spaces between them.

The full regular expression: -?\d+(\.\d+)?\s*[-+*/]\s*-?\d+(\.\d+)?.

It has 3 parts, with \s* between them:

  1. -?\d+(\.\d+)? – the first number,
  2. [-+*/] – the operator,
  3. -?\d+(\.\d+)? – the second number.

To make each of these parts a separate element of the result array, let’s enclose them in parentheses: (-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?).

In action:

let regexp = /(-?\d+(\.\d+)?)\s*([-+*\/])\s*(-?\d+(\.\d+)?)/;

alert( "1.2 + 12".match(regexp) );

The result includes:

  • result[0] == "1.2 + 12" (full match)
  • result[1] == "1.2" (first group (-?\d+(\.\d+)?) – the first number, including the decimal part)
  • result[2] == ".2" (second group(\.\d+)? – the first decimal part)
  • result[3] == "+" (third group ([-+*\/]) – the operator)
  • result[4] == "12" (forth group (-?\d+(\.\d+)?) – the second number)
  • result[5] == undefined (fifth group (\.\d+)? – the last decimal part is absent, so it’s undefined)

We only want the numbers and the operator, without the full match or the decimal parts, so let’s “clean” the result a bit.

The full match (the arrays first item) can be removed by shifting the array result.shift().

Groups that contain decimal parts (number 2 and 4) (.\d+) can be excluded by adding ?: to the beginning: (?:\.\d+)?.

The final solution:

function parse(expr) {
  let regexp = /(-?\d+(?:\.\d+)?)\s*([-+*\/])\s*(-?\d+(?:\.\d+)?)/;

  let result = expr.match(regexp);

  if (!result) return [];
  result.shift();

  return result;
}

alert( parse("-1.23 * 3.45") );  // -1.23, *, 3.45
خريطة الدورة التعليمية