٢٥ مارس ٢٠٢١

Lookahead و lookbehind

Sometimes we need to find only those matches for a pattern that are followed or preceded by another pattern.

هناك صيغة خاصة لذلك ، تسمى “lookahead” و “lookbehind” ، يشار إليها معًا باسم “lookaround”.

في البداية ، دعنا نجد السعر من السلسلة مثل الموضوع: 1 ديك رومي يكلف 30 يورو. أي: رقم متبوعًا بعلامة .

Lookahead

الصيغة هي: X (؟ = Y) ، وتعني "ابحث عن X ، لكن تطابق فقط إذا تبعها "pattern: Y". قد يكون هناك أي نمط بدلاً منpattern:Xوpattern:Y`.

بالنسبة لرقم صحيح متبوعًا بـ, سيكون regexp \d+(?=€):

let str = '1 turkey costs 30€';

alert(str.match(/\d+(?=€)/)); // 30, the number 1 is ignored, as it's not followed by €

يرجى ملاحظة: lookahead هو مجرد اختبار ، ومحتويات "نمط قوسين: (؟ = …)غير مدرجة في النتيجةmatch:30`.

عندما نبحث عن "pattern: X (؟ = Y)` ، يعثر محرك التعبير العادي على “pattern: X” ثم يتحقق مما إذا كان هناك “pattern: Y” بعده مباشرة. إذا لم يكن الأمر كذلك ، يتم تخطي المطابقة المحتملة ، ويستمر البحث.

من الممكن إجراء اختبارات أكثر تعقيدًا ، على سبيل المثال النمط: X (؟ = Y) (؟ = Z) يعني:

  1. ابحث عن النمط: X.
  2. تحقق مما إذا كان “النمط: Y” مباشرةً بعد “النمط: X” (يمكنك التخطي إذا لم يكن كذلك).
  3. تحقق مما إذا كان “النمط: Z” هو أيضًا مباشرةً بعد “النمط: X” (يمكنك التخطي إذا لم يكن كذلك).
  4. في حالة اجتياز كلا الاختبارين ، فإن “النمط: X” هو تطابق ، وإلا فتابع البحث.

بمعنى آخر ، يعني هذا النمط أننا نبحث عن X متبوعًا بـ Y و Z في نفس الوقت.

هذا ممكن فقط إذا كان النموذجان “pattern: Y” و “pattern: Z” لا يستبعد أحدهما الآخر.

على سبيل المثال ، \ d + (؟ = \ s) (؟ =. * 30) يبحث عن \ d + فقط إذا كان متبوعًا بمسافة ، ويوجد 30 في مكان ما بعده:

For example, \d+(?=\s)(?=.*30) looks for \d+ that is followed by a space (?=\s), and there’s 30 somewhere after it (?=.*30):

let str = '1 turkey costs 30€';

alert(str.match(/\d+(?=\s)(?=.*30)/)); // 1

في سلسلتنا التي تتطابق تمامًا مع الرقم 1.

Negative lookahead

لنفترض أننا نريد كمية بدلاً من ذلك ، وليس سعرًا من نفس السلسلة. هذا رقم نمط: \ d + ، وليس متبوعًا بـ الموضوع: €.

لذلك ، يمكن تطبيق lookahead سلبي.

الصيغة هي: X (؟! Y) ، وتعني "search X ، ولكن فقط إذا لم يتبعها “pattern: Y`”.

let str = '2 turkeys cost 60€';

alert(str.match(/\d+\b(?!€)/g)); // 2 (the price is not matched)

Lookbehind

يسمح Lookahead بإضافة شرط لـ “ما يلي”.

Lookbehind مشابه ، لكنه يبدو في الخلف. أي أنه يسمح بمطابقة النمط فقط إذا كان هناك شيء قبله.

الصيغة هي:

  • نظرة إيجابية خلف: (؟ <= Y) X ، تطابق X ، ولكن فقط في حالة وجود "pattern: Y` قبلها.
  • مظهر سلبي خلف: (؟ <! Y) X ، يطابق X ، ولكن فقط في حالة عدم وجود Y قبله.

على سبيل المثال ، دعنا نغير السعر إلى الدولار الأمريكي. عادةً ما تكون علامة الدولار قبل الرقم ، لذلك للبحث عن $ 30 ، سنستخدمالنمط: (؟ <= \ $) \ d +- مبلغ يسبقهالموضوع: $`:

let str = '1 turkey costs $30';

// the dollar sign is escaped \$
alert(str.match(/(?<=\$)\d+/)); // 30 (skipped the sole number)

وإذا احتجنا إلى الكمية – رقمًا ، لا يسبقه "الموضوع: $، فيمكننا استخدام النمط السلبي خلف ": (؟ <! \ $) \ d +:

let str = '2 turkeys cost $60';

alert(str.match(/(?<!\$)\b\d+/g)); // 2 (the price is not matched)

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

بشكل عام ، لا تصبح المحتويات الموجودة داخل الأقواس حول جزء من النتيجة.

على سبيل المثال في النموذج \ d + (؟ = €) ، لا يتم التقاط علامة كجزء من المباراة. هذا طبيعي: نحن نبحث عن رقم نقش: \ d + ، بينما نقش: (؟ = €) هو مجرد اختبار يجب أن يتبعه الموضوع: €.

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

في المثال أدناه ، تم تسجيل “نمط علامة العملة: (€ | kr)” ، بالإضافة إلى المبلغ:

let str = '1 turkey costs 30€';
let regexp = /\d+(?=(€|kr))/; // extra parentheses around €|kr

alert(str.match(regexp)); // 30, €

وإليك نفس الشيء بالنسبة إلى: lookbehind:

let str = '1 turkey costs $30';
let regexp = /(?<=(\$|£))\d+/;

alert(str.match(regexp)); // 30, $

ملخص

Lookahead و lookbehind (يشار إليهما عادةً باسم “lookaround”) مفيدان عندما نرغب في مطابقة شيء ما اعتمادًا على السياق قبله / بعده.

بالنسبة إلى regexps البسيطة ، يمكننا القيام بنفس الشيء يدويًا. هذا هو: مطابقة كل شيء ، في أي سياق ، ثم التصفية حسب السياق في الحلقة.

تذكر أن str.match (بدون العلامة g) و str.matchAll (دائمًا) ترجع التطابقات كمصفوفات مع خاصيةindex ، حتى نعرف مكانها بالضبط في النص ، ويمكننا التحقق من سياق الكلام.

لكن بشكل عام يكون البحث أكثر ملاءمة.

أنواع Lookaround:

النمط النوع التطابق
X(?=Y) Positive lookahead X إذا تبعه Y
X(?!Y) Negative lookahead ``pattern: Xإذا لم يتبعه pattern: Y`
(?<=Y)X Positive lookbehind X إذا بعده Y
(?<!Y)X Negative lookbehind X إذا لم يكن بعده Y

مهمه

هناك سلسلة من الأعداد الصحيحة. أنشئ تعبيرًا عاديًا لا يبحث إلا عن الكلمات غير السلبية (يُسمح بصفر).

مثال للاستخدام:

let regexp = /your regexp/g;

let str = "0 12 -5 123 -18";

alert( str.match(regexp) ); // 0, 12, 123

regexp لرقم صحيح هو \ d +.

We can exclude negatives by prepending it with the negative lookbehind: (?<!-)\d+.

على الرغم من أننا إذا جربناها الآن ، فقد نلاحظ نتيجة “إضافية” أخرى:

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

let str = '0 12 -5 123 -18';

console.log(str.match(regexp)); // 0, 12, 123, 8

كما ترون ، فإنه يطابق المباراة: 8 ، منالموضوع: -18. لاستبعاده ، نحتاج إلى التأكد من أن regexp يبدأ في مطابقة رقم ليس من منتصف رقم آخر (غير مطابق).

يمكننا القيام بذلك عن طريق تحديد مظهر سلبي آخر خلف: (؟ <! -) (؟ <! \ d) \ d +. الآن النمط: (؟ <! \ d) يضمن أن المطابقة لا تبدأ بعد رقم آخر ، فقط ما نحتاجه.

يمكننا أيضًا أن ننضم إليهم في lookbehind خلفنا هنا:

let regexp = /(?<![-\d])\d+/g;

let str = '0 12 -5 123 -18';

alert(str.match(regexp)); // 0, 12, 123

لدينا سلسلة مع مستند HTML.

اكتب تعبيرًا عاديًا يُدرج <h1> مرحبًا </ h1> مباشرة بعد علامة <body>. قد يكون للسمات سمات.

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

let regexp = /your regular expression/;

let str = `
<html>
  <body style="height: 200px">
  ...
  </body>
</html>
`;

str = str.replace(regexp, `<h1>Hello</h1>`);

بعد هذا من المفترض أن تصبح قيمة str:

<html>
  <body style="height: 200px"><h1>Hello</h1>
  ...
  </body>
</html>

In order to insert after the <body> tag, we must first find it. We can use the regular expression pattern <body.*?> for that.

في هذه المهمة ، لا نحتاج إلى تعديل علامة <body>. نحتاج فقط لإضافة النص بعده.

إليك كيفية القيام بذلك:

let str = '...<body style="...">...';
str = str.replace(/<body.*?>/, '$&<h1>Hello</h1>');

alert(str); // ...<body style="..."><h1>Hello</h1>...

In the replacement string $& means the match itself, that is, the part of the source text that corresponds to <body.*?>. It gets replaced by itself plus <h1>Hello</h1>.

البديل هو استخدام lookbehind:

let str = '...<body style="...">...';
str = str.replace(/(?<=<body.*?>)/, `<h1>Hello</h1>`);

alert(str); // ...<body style="..."><h1>Hello</h1>...

كما ترون ، هناك فقط جزء وراء النظر في هذا التعبير العادي.

It works like this:

  • At every position in the text.
  • Check if it’s preceeded by <body.*?>.
  • If it’s so then we have the match.

The tag <body.*?> won’t be returned. The result of this regexp is literally an empty string, but it matches only at positions preceeded by <body.*?>.

So it replaces the “empty line”, preceeded by <body.*?>, with <h1>Hello</h1>. That’s the insertion after <body>.

P.S. Regexp flags, such as s and i can also be useful: /<body.*?>/si. The s flag makes the dot . match a newline character, and i flag makes <body> also match <BODY> case-insensitively.

خريطة الدورة التعليمية