اذهب إلى المحتوى

كتابة تعابير نمطية RegEx متقدمة في جافاسكربت


ابراهيم الخضور

بعد أن تعرفنا على أساسيات التعابير النمطية ثم تعرفنا على المجموعات ثم المحددات الكمية ثم تعلمنا كيفية التقاط عدة مجموعات عبر التعابير النمطية، سنقدم فيما يلي مجموعةً من الأفكار المتقدمة في بناء وتنفيذ تعابير نمطية أكثر كفاءةً في البحث عن التطابقات المطلوبة، مثل المراجع References، والمحرف البديل OR، والبحث قُدُمًا lookahead، والبحث إلى الخلف lookbehind، والبحث في موقع محدد باستخدام الراية y.

المراجع إلى المجموعات بالأعداد والأسماء

يمكن استخدام محتويات أقواس المجموعات الملتقطة (...) في النتيجة أو في النص البديل، وحتى في النمط نفسه.

المراجع باستخدام الأعداد

يمكن الإشارة إلى مجموعة ضمن نمط باستخدام N\، حيث N هو رقم المجموعة، ولتوضيح أهمية هذه الإشارة المرجعية لنتأمل المهمة التالية: نريد إيجاد ما داخل إشارتي التنصيص المفردتين '...' أو المزدوجتين "..." في نص ما، فكيف سننجز ذلك؟

يمكن وضع نوعي إشارة التنصيص في أقواس مربعة ['"](?*.)['"]، لكنه في هذه الحالة سيجد محتويات مختلطةً، مثل "...' و'..."، مما سيقودنا إلى تطابقات خاطئة عندما يظهر نوع من الإشارات ضمن آخر، مثل "!She's the one".

let str = `He said: "She's the one!".`;

let regexp = /['"](.*?)['"]/g;

// النتيجة ليست كما نتوقع
alert( str.match(regexp) ); // "She'

وجد النمط -كما توقعنا- إشارة تنصيص البداية "، ثم استهلك النص بعدها حتى وجد إشارة تنصيص أخرى ' أنهت التطابق، وللتأكد من مطابقة إشارة التنصيص الختامية لإشارة تنصيص البداية، يمكن وضعها ضمن قوسي مجموعة، والإشارة إليها بعدد مرجعي 1\(?*.)(["'])

إليك الشيفرة الصحيحة:

let str = `He said: "She's the one!".`;

let regexp = /(['"])(.*?)\1/g;

alert( str.match(regexp) ); // "She's the one!"

سيجد محرك التعبير النمطي إشارة تنصيص البداية (["'])، ويتذكر محتواها الذي يمثل المجموعة الملتقطة الأولى، حيث يعني العدد 1\ في النمط إيجاد نفس التطابق الموجود في المجموعة الأولى، وهي في حالتنا إشارة تنصيص تطابق تمامًا إشارة البداية.

وسيعني العدد 2\ محتويات المجموعة الثانية، والعدد 3\ محتويات الثالثة، وهكذا.

لاحظ، لن نتمكن من الإشارة إلى مجموعة إذا استخدمنا نمط الاستثناء :?، ولن يتذكر المحرك محتوى المجموعات المستثناة (...:?).

انتبه، لا تخلط بين الإشارة 1\ في نمط ما، والإشارة 1$ في النص البديل، حيث نستخدم إشارة الدولار 1$ في الإشارة المرجعية ضن النص البديل، والشرطة المعكوسة 1\ ضمن الأنماط.

المراجع باستخدام الأسماء

إذا احتوى التعبير النمطي على عدة أقواس، فمن الأنسب استخدام الأسماء للدلالة عليها، نستخدم <k<name\ في الدلالة على القوس بالاسم، فإذا سُميت مجموعة ضمن التعبير النمطي بالاسم <quote>?، فسيكون الاسم المرجعي لها هو <k<quote\، وإليك مثالًا:

let str = `He said: "She's the one!".`;

let regexp = /(?<quote>['"])(.*?)\k<quote>/g;

alert( str.match(regexp) ); // "She's the one!"

البديل باستخدام OR في التعابير النمطية

يعني مصطلح "البديل" Alternation في التعابير النمطية استخدام العملية المنطقية "OR"، ويرمز لها ضمن التعابير النمطية بالخط العمودي |، فلو أردنا مثلًا إيجاد لغات برمجة مثل HTML أو PHP أو Java أو JavaScript، فسيكون التعبير النمطي المناسب هو ?(html|php|java(script، وإليك مثالًا تطبيقيًا:

let regexp = /html|php|css|java(script)?/gi;

let str = "First HTML appeared, then CSS, then JavaScript";

alert( str.match(regexp) ); // 'HTML', 'CSS', 'JavaScript'

لكننا رأينا سابقًا أنّ الأقواس المربعة تنفذ أمرًا مشابهًا، فهي تسمح باختيار أحد المحارف التي توضع ضمنها، فإذا استخدمنا النمط gr[ae]y مثلًا فسنحصل على التطابقين gray أو grey، إذ تسمح الأقواس المربعة باستخدام المحارف أو أصناف المحارف ضمنها، بينما يسمح البديل باستخدام أي عبارات، حيث يعني التعبير A|B|C أيًا من العبارات A أو B أو C، وإليك بعض الأمثلة:

  • يماثل النمط gr(a|e)y النمط gr[ae]y.
  • يعني النمط gra|ey أيًا من gra أو ey.

ولتطبيق البديل على جزء محدد من نمط، يمكن وضع هذا الجزء داخل قوسين:

  • يطابق النمط I love HTML|CSS كلًا من I love HTML أو CSS.
  • يطابق النمط (I love (HTML|CSS كلًا من I love HTML أو I love CSS

مثال: تعبير نمطي لإيجاد الوقت

صادفنا في المقالات السابقة مهمة بناء تعبير نمطي يبحث عن الوقت وفق التنسيق hh:mm، مثل 12:00، لكن التعبير الذي استُخدم d\d:\d\d\ سطحي جدًا، إذ يقبل هذا النمط قيمًا خاطئة للتوقيت مثل 25:99، فكيف سننجز نمطًا أفضل؟

بالنسبة للساعات:

  • إذا كان الرقم الأول 0 أو1 فيمكن أن يكون الثاني أي رقم.
  • إذا كان الرقم الأول 2، فيجب أن يكون الثاني [‎0-3].
  • لا يسمح بأي رقم آخر غير ذلك.

يمكن كتابة تعبير نمطي يضم الحالتين باستخدام البديل بالشكل التالي:

[01]\d|2[0-3]

أما بالنسبة للدقائق: ينبغي أن تكون الدقائق بين 00 و59، ويكتب هذا في التعبير النمطي بالشكل ‎[0-5]\d، أي يمكن أن تكون الآحاد أي رقم، والعشرات من 1 إلى 5.

وعند ضم الساعات والدقائق معًا سنحصل على التعبير:

[01]\d|2[0-3]:[0-5]\d

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

[01]\d    |    2[0-3]:[0-5]\d

إليك ما سيحدث، سيبحث النمط عن أحد النمطين، لكن هذا خاطئ، لأنّ البديل سيستخدَم فقط في قسم الساعات من التعبير النمطي، وسنصلح ذلك بوضع قسم الساعات ضمن قوسين

([01]\d|2[0-3]):[0-5]\d

إليك الحل النهائي:

let regexp = /([01]\d|2[0-3]):[0-5]\d/g;

alert("00:00 10:10 23:59 25:99 1:2".match(regexp)); // 00:00,10:10,23:59

التحقق مما يلي أو يسبق التعبير النمطي

نحتاج في بعض الأحيان إلى إيجاد تطابقات بشرط أن يأتي بعدها أو قبلها تطابقًا أو نمطًا محددًا دون أن تدخل تلك التطابقات ضمن قيم النتيجة النهائية، ولهذه الغاية سنجد صيغتا تحقق تُدعيان "انظر أمام النمط" lookahead،" وانظر خلف النمط "lookbehind،" وقبل أن نبدأ موضوعنا، سنأخذ مثالًا نحاول فيه إيجاد السعر ضمن النص ‎1 turkey costs 30€، وهو عدد تتبعه الإشارة .

انظر أمام التعبير النمطي

النمط (X(?=Y يعني "ابحث عن X، لكن أعد نتيجة التطابق فقط إذا كان بعدها Y"، ويمكن أن تجد أي نمط مكان X أو Y، فمن أجل عدد صحيح تتبعه الإشارة ، سيكون النمط المناسب هو (€=?)+d\:

let str = "1 turkey costs 30€";

alert( str.match(/\d+(?=€)/) ); // 30, € يهمل الرقم 1 لأنه غير متبوع بالإشارة

لاحظ أنّ مطابقة ما أمام النمط هو اختبار وعملية تحقيق فقط ببساطة، ولا يشكل محتوى ما بين القوسين جزءًا من النتيجة، فعندما نبحث عن النمط (X(?=Y، فسيجد المحرك X ومن ثم يتحقق من وجود Y بعدها مباشرةً إن وجده أعاد X فقط وإلا لم يعد شيئًا، ثم يتابع البحث.

يمكن كتابة اختبارات أكثر تعقيدًا، مثل النمط (x(?=y)(?=z الذي يعني:

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

أي أننا نبحث عن X التي تتبعها القيمتان Y وZ في نفس الوقت.

وسيبحث النمط (d+(?=\s)(?=.*30\ عن عدد +d\ يتبعه فراغ (s\=?)، ثم العدد 30 في مكان ما من النص:

let str = "1 turkey costs 30€";

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

التطابق الوحيد المناسب في المثال السابق هو 1.

لنفترض بأننا سنحتاج إلى الكمية وليس السعر من النص نفسه، وبالتالي سنحتاج إلى عدد +d\ غير متبوع بالإشارة ، يمكن إنجاز ذلك باستعمال النمط الاختباري (x(?!y، أي "انظر أمام النمط، يجب أن لا يتحقق الشرط" بمعنى ابحث عن X التي لا يتبعها Y مباشرةً.

let str = "2 turkeys cost 60€";

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

انظر خلف التعبير النمطي

يشابه هذا النمط نمط التحقق مما يلي التعبير، لكنه يضع شرطًا على ما قبل التعبير المتطابق ويمكن أن نستخدمه بالصيغ التالية:

  • "التحقق من مطابقة ما يسبق التعبير" Positive lookbehind عبر الصيغة Y)X=>?): أوجد X إذا سبقتها Y.
  • "التحقق من عدم مطابقة ما يسبق التعبير" Negative lookbehind عبر الصيغة Y)X!>?): أوجد X إذا لم تسبقها Y.

لنغير على سبيل المثال السعر إلى دولار، حيث تكون إشارة الدولار $ عادةً قبل السعر، فللبحث عن 30$ سنستخدم النمط +d\($\=>?):

let str = "1 turkey costs $30";

//  \$ سنتجاوز الإشارة
alert( str.match(/(?<=\$)\d+/) ); // 30 

وإذا أردنا الكمية، فسنبحث عن عدد لا تسبقه الإشارة $، وبالتالي نستخدم البحث السلبي خلفًا +d\($\!>?):

let str = "2 turkeys cost $60";

alert( str.match(/(?<!\$)\b\d+/g) ); // 2 

إدخال الشرط ضمن نتيجة التطابق

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

let str = "1 turkey costs 30€";
let regexp = /\d+(?=(€|kr))/; //  €|kr أقواس إضافية حول 

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

إليك مثالًا مع البحث خلفًا:

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

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

الراية y والبحث في موقع محدد

تسمح الراية y بالبحث في موقع محدد ضمن النص الأصلي، ولفهم عمل هذه الراية وأساليب استخدامها في التعابير النمطية جيدًا سنطرح مشكلةً عمليةً، حيث تعتبر عملية التحليل التجريدي لنص lexical analysis من المهام الأساسية لمحركات التعابير النمطية، ففي نصوص الشيفرة في لغات البرمجة تلعب محركات التعابير الدور الأساسي في إيجاد العناصر البنيوية، مثل وسوم HTML والسمات التي في داخلها، وستساعد في JavaScript على إيجاد المتغيرات والدوال وغيرها، ولن ندخل في موضوع إنشاء المحللات التجريدية فهي خارج نطاق موضوعنا، لكن توجد مهمة مشتركة وهي إيجاد شيء ما في موقع محدد، لنفترض مثلًا أنّ لدينا الشيفرة التالية "let varName = "value في صيغة نص، ونحتاج إلى تحديد اسم المتغير الذي سيبدأ في الموقع الرابع، سنبحث عن اسم المتغير من خلال التعبير +w\، وطبعًا نحتاج إلى تعابير أكثر تعقيدًا للبحث الدقيق عن أسماء المتغيرات في JavaScript لكن لن نهتم بذلك حاليًا.

  • سيجد التابع (/+str.match(/\w الكلمة الأولى فقط في السطر وهي (let)، وطبعًا ليست هي الكلمة المطلوبة.
  • ولو استخدمنا الراية g فسيبحث التابع عن جميع الكلمات في النص، لكننا لا نحتاج سوى الكلمة الموجودة في الموقع الرابع.

كيف سنبحث عن نمط محدد ابتداءً من موقع محدد؟ لنحاول استخدام التابع (regexp.exec(str، حيث سيبحث هذا التابع عن التطابق الأول بطريقة مشابهة للتابع (str.match(regexp عندما يُستخدم دون الرايتين g أو y، لكنه سيبحث في النص ابتداءً من الموقع المخزن في الخاصية regexp.lastIndex عندما نفعّل الراية g، فإذا وجد تطابقًا فسيخزن الموقع الذي يلي التطابق في الخاصية regexp.lastIndex، أي ستكون الخاصية regexp.lastIndex نقطة بداية للبحث، وسيغير كل استدعاء للتابع (regexp.exec(str قيمتها إلى القيمة الجديدة -بعد إيجاد تطابق-، وبالتالي سيعيد الاستدعاء المتكرر للتابع (regexp.exec(str التطابقات واحدًا تلو الآخر، وإليك مثالًا:

let str = 'let varName'; // لنبحث عن كل الكلمات في هذه السلسلة
let regexp = /\w+/g;

alert(regexp.lastIndex); // 0 ( lastIndex=0)

let word1 = regexp.exec(str);
alert(word1[0]); // let (الكلمة الأولى)
alert(regexp.lastIndex); // 3 (الموقع بعد التطابق)

let word2 = regexp.exec(str);
alert(word2[0]); // varName (الكلمة الثانية)
alert(regexp.lastIndex); // 11 (الموقع بعد التطابق)

let word3 = regexp.exec(str);
alert(word3); // null (لا تطابقات)
alert(regexp.lastIndex); // 0 (يصفر قيمة الخاصية)

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

let str = 'let varName';
let regexp = /\w+/g;

let result;

while (result = regexp.exec(str)) {
  alert( `Found ${result[0]} at position ${result.index}` );
  // Found let at position 0, then
  // Found varName at position 4
}

يمثل استخدام التابع regexp.exec أسلوبًا بديلًا عن str.matchAll مع بعض السيطرة على مجرى العملية.

بالعودة إلى مهمتنا، سنتمكن من تحديد الموقع بإسناد القيمة 4 إلى الخاصية lastIndex لنبدأ البحث من الموقع المطلوب بالشكل التالي:

let str = 'let varName = "value"';

let regexp = /\w+/g; // "g" دون استخدام lastIndex يتجاهل المحرك الخاصية

regexp.lastIndex = 4;

let word = regexp.exec(str);
alert(word); // varName

لقد حُلِّت المشكلة! والنتيجة صحيحة، لكن ينبغي أن نتروّى قليلًا..

يبدأ التابع regexp.exec البحث انطلاقًا من الموقع lastIndex، ثم يستأنف البحث، فإذا لم يكن التطابق موجودًا في هذا الموقع بل في موقع يأتي بعده فسيجده أيضًا:

let str = 'let varName = "value"';

let regexp = /\w+/g;

// ابدأ البحث في الموقع 3
regexp.lastIndex = 3;

let word = regexp.exec(str);
// سيجد التطابق في الموقع 4
alert(word[0]); // varName
alert(word.index); // 4

إنّ هذا الأمر خاطئ في العديد من المهام ومنها التحليل التجريدي، إذ يجب إيجاد التطابق في الموقع المحدد تمامًا وليس بعده، وهذا سبب وجود الراية y. حيث تجبر الراية y التابع regexp.exec على البحث في الموقع lastIndex بالتحديد وليس انطلاقًا منه.

إليك مثالًا:

let str = 'let varName = "value"';

let regexp = /\w+/y;

regexp.lastIndex = 3;
alert( regexp.exec(str) ); // null (يوجد فراغ في هذا الموقع وليس كلمة)

regexp.lastIndex = 4;
alert( regexp.exec(str) ); // varName (الكلمة في الموقع 4)

لاحظ أننا لن نحصل على التطابق مع التعبير w+/y\/ في الموقع 3 (على عكس g) لكن في الموقع 4، وليس هذا فحسب، بل توجد فائدة أخرى لاستخدام الراية y، تخيل أن لدينا نصًا طويلًا ولا توجد فيه أية تطابقات للنمط الذي نبحث عنه، سيبحث المحرك عند تفعيل الراية g حتى نهاية النص ولن يجد نتيجةً، وهذا ما سيستهلك الوقت مقارنةً باستخدام الراية y التي تتحقق فقط في الموقع المحدد.

سنواجه في المهام التي تشبه التحليل التجريدي عمليات بحث متعددةً في مواقع محددة، للتحقق مما هو موجود، وسيكون استخدام الراية y المفتاح لإنجاز مثل هذه العمليات بدقة وفعالية.

خلاصة

  • يفيدنا التحقق مما يلي التطابق ويسبقه في إيجاد تطابق بناءً على ما قبله أو بعده.
  • يمكن استخدام تعابير نمطية بسيطة لإنجاز ذلك يدويًا، كأن نبحث عن كل التطابقات المطلوبة ونرشحها من خلال الحلقات وفق السياق المطلوب.
  • تذكر أن التابع str.match (مع الراية g) والتابع str.match (دائمًا) سيعيدان مصفوفةً لها الخاصية index، وبالتالي سنعلم تمامًا موقع التطابق في النص، ثم نتحقق مما قبله أو بعده.
  • إذا نفِّذ البحث خلفًا أو قدمًا آليًا فسيكون أفضل من الطريقة اليدوية، مثلما فعلنا في هذا المقال:
النمط نوع البحث ماذا يُطابق؟
(X(?=Y بحث إيجابي قُدمًا X إذا جاءت بعدها Y
(X(?!Y بحث سلبي قدمًا X إذا لم تكن بعدها Y
y)x=>?) بحث إيجابي خلفًا X إذا جاءت بعد Y
y)x!>?) بحث سلبي خلفًا X إذا لم تأت بعد Y
  • تجبر الراية y التابع regexp.exec على البحث في الموقع lastIndex بالتحديد وليس انطلاقًا منه.

مهام لإنجازها

إيجاد لغات البرمجة

اكتب تعبيرًا نمطيًا يبحث عن أسماء لغات البرمجة الموجودة في النص Java JavaScript PHP C++ C

let regexp = /your regexp/g;

alert("Java JavaScript PHP C++ C".match(regexp)); // Java JavaScript PHP C++ C

الحل: تقتضي الفكرة الأولى بترتيب اللغات بحيث يفصل بينها المحرف|، لكن هذا الحل لن ينفع:

let regexp = /Java|JavaScript|PHP|C|C\+\+/g;

let str = "Java, JavaScript, PHP, C, C++";

alert( str.match(regexp) ); // Java,Java,PHP,C,C

سيبحث محرك التعابير النمطية عن البدائل واحدًا تلو الآخر، فلو تحقق بداية من "Java" ووجدها، فلن يجد بالطبع "JavaScript" وكذلك الأمر بالنسبة للغتين "C" و "++C".

هنالك حلين للمشكلة:

  • غير ترتيب اللغات لتكون اللغة ذات الاسم الأطول أولًا:
JavaScript|Java|C\+\+|C|PHP
  • ادمج اللغات التي لها نفس البداية :
 Java(Script)?|C(\+\+)?|PHP

إليك الشيفرة:

let regexp = /Java(Script)?|C(\+\+)?|PHP/g;

let str = "Java, JavaScript, PHP, C, C++";

alert( str.match(regexp) ); // Java,JavaScript,PHP,C,C++

إيجاد زوجي وسوم [tag]…[/tag]

لنفترض وجود زوج الوسوم [tag]...[/tag]، حيث يمثّل tag أحد العناصر b أو url أو quote، إليك مثالًا:

[b]text[/b]
[url]http://google.com[/url]

قد تكون هذه الوسوم متداخلةً، لكن لا يمكن أن يتداخل العنصر مع نفسه:

Normal:
[url] [b]http://google.com[/b] [/url]
[quote] [b]text[/b] [/quote]

Can't happen:
[b][b]text[/b][/b]

ليس بالضرورة أن تكون وسوم البداية والنهاية لعنصر على نفس السطر:

[quote]
  [b]text[/b]
[/quote]

اكتب تعبيرًا نمطيًا للبحث عن تلك الوسوم ومحتوياتها، إليك مثالًا:

let regexp = /your regexp/flags;

let str = "..[url]http://google.com[/url]..";
alert( str.match(regexp) ); // [url]http://google.com[/url]

إذا كانت الوسوم متداخلةً فنريد فقط العنصر الخارجي:

let regexp = /your regexp/flags;

let str = "..[url][b]http://google.com[/b][/url]..";
alert( str.match(regexp) ); // [url][b]http://google.com[/b][/url]

الحل: يُعطى التعبير النمطي للبحث عن وسم البداية بالشكل:

\[(b|url|quote)]

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

\[(b|url|quote)\].*?\[/\1]

إليك الشيفرة:

let regexp = /\[(b|url|quote)].*?\[\/\1]/gs;

let str = `
  [b]hello![/b]
  [quote]
    [url]http://google.com[/url]
  [/quote]
`;

alert( str.match(regexp) ); // [b]hello![/b],[quote][url]http://google.com[/ur

إيجاد ما بين إشارتي التنصيص "…."

أنشئ تعبيرًا نمطيًا لإيجاد سلسلة نصية بين إشارتي تنصيص"….".

يجب أن يدعم النص فكرة تجاوز المحارف تمامًا كما في جافا سكربت. أي يمكن أن نضع علامة التنصيص بالشكل "\ وأن نضع علامة السطر الجديد بالشكل n\ والشرطة المائلة بالشكل \\.

let str = "Just like \"here\".";

وانتبه إلى أن علامة التنصيص التي يجري تجاوزها "\ لن تعتبر نهاية للسلسلة النصية. وبالتالي لا بدّ من البحث من إشارة تنصيص إلى أخرى متجاهلين علامات التنصيص التي يجري تجاوزها.

أمثلة عن نصوص وتطابقاتها:

.. "test me" ..
.. "Say \"Hello\"!" ... (داخلها علامة تنصيص جرى تجاوزها)
.. "\\" ..  (داخلها شرطة مائلة مزدوجة)
.. "\\ \"" ..  (داخلها شرطة مائلة مزدوجة وعلامة تنصيص جرى تجاوزها)

لا بد في جافاسكربت من مضاعفة الشرطات المائلة لتمريرها إلى النصوص كالتالي:

let str = ' .. "test me" .. "Say \\"Hello\\"!" .. "\\\\ \\"" .. ';

// the in-memory string
alert(str); //  .. "test me" .. "Say \"Hello\"!" .. "\\ \"" ..

الحل:

/"(\\.|[^"\\])*"/g

نتبع الآتي خطوةً بخطوة:

  • نبحث أولًا عن إشارة تنصيص البداية ".
  • إن كان هناك شرطة عكسية \\ لا بد من مضاعفتها في التعبير لأنها محرف خاص. بعدها يمكن أن يأتي أي محرف.
  • باستثناء الحالتين السابقتين يمكن أن نأخذ أية محارف ما عدا إشارة التنصيص (التي تعني نهاية النص) والشرطة العكسية (تستخدم الشرطة العكسية فقط مع رموز أخرى بعدها لمنع ظهورها مفردة): [^"\\]
  • وهكذا حتى نصل إلى إشارة تنصيص نهاية النص.

إليك الشيفرة:

let regexp = /"(\\.|[^"\\])*"/g;
let str = ' .. "test me" .. "Say \\"Hello\\"!" .. "\\\\ \\"" .. ';

alert( str.match(regexp) ); // "test me","Say \"Hello\"!","\\ \""

إيجاد وسم بأكمله

اكتب تعبيرًا نمطيًا لإيجاد الوسم <style> كاملًا. قد لا يضم الوسم أية صفات attributes أو عددًا منها:

<style type="..." id="...">

لا يجب أن يطابق التعبير الوسم <styler> مثلًا:

let regexp = /your regexp/g;

alert( '<style> <styler> <style test="...">'.match(regexp) ); // <style>, <style test="...">

الحل: من المؤكد أن بداية النمط هي <style. لكن لا يمكن أن تكون بقية النمط ببساطة <?*.style> لأن وسمًا مثل <styler> سيكون نتيجة محتملة. نحتاج إلى فراغ بعد <style ثم محارف اختيارية بعده أو النهاية، وبالتالي سيكون النمط المطلوب:

<style(>|\s.*?>)

إليك الشيفرة:

let regexp = /<style(>|\s.*?>)/g;

alert( '<style> <styler> <style test="...">'.match(regexp) ); // <style>, <style test

إيجاد أعداد صحيحة موجبة

لدينا سلسلة نصية من الأعداد الصحيحة . أنشئ تعبيرًا نمطيًا يبحث عن الأعداد الموجبة فقط (يُسمح بالتقاط الصفر) مثال عن الاستخدام:

let regexp = /your regexp/g;

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

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

الحل: التعبير النمطي المناسب لإيجاد عدد صحيح هو :

 \d+

يمكن التخلص من الأعداد السالبة باستخدام النظر خلفًا إلى الإشارة السالبة :

 (?<!-)\d+

لو استخدمنا هذا النمط فقد نلاحظ ظهور نتيجة إضافية:

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

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

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

كما ترى فقد وجد 8 بدلًا من -18. لحل المشكلة، لا بدّ من التحقق أن التعبير النمطي لن يبدأ المطابقة من منتصف رقم آخر غير مطابق. يمكن إنجاز الأمر بتخصيص عملية بحث أخرى للخلف عن الإشارة السالبة :

(?<!-)(?<!\d)\d+

يتحقق النمط

 (?<!\d)

أن المطابقة لن تبدأ بعد رقم آخر، بل فقط ما نحتاجه، ويمكن ضمهما أيضًا في عملية بحث للخلف واحدة: .

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

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

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

إضافة شيفرة بعد الوسم Head

لدينا نص ضمن مستند HTML. اكتب تعبيرًا نمطيًا يدخل الشيفرة <h1>Hello</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>

الحل: علينا أولًا إيجاد الوسم <body>وذلك باستخدام التعبير النمطي:

<body.*?>

لا حاجة لتعديل الوسم <body> بل فقط إضافة النص بعده. إليك ما يمكن فعله:

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

يعني النمط في النص المُستبدل التطابق الذاتي أي الجزء من النص الأصل المرتبط بالتعبير النمطي <?*. body> حيث سيُستبدل بنفسه إضافة إلى "<h1>Hello</h1>".

يقتضي الحل البديل استخدام البحث خلفًا:

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

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

لاحظ وجود جزء البحث خلفًا فقط في هذا التعبير النمطي حيث يعمل كالتالي:

  • تحقق عند كل موقع في النص إن تبعه النمط <?*. body>.
  • إن كان الأمر كذلك فقد حصلنا على التطابق. لن يُعاد الوسم <?*. body> وستكون النتيجة نصًا فارغًا يتطابق الموقع الذي يتبعه النمط <?*. body>. عندها سيُستبدل النص الفارغ بالنمط <?*. body> يليه النص <h1>Hello</h1>. يمكن الاستفادة من الرايات s و i في النمط كالتالي:
/<body.*?>/si

تدفع الراية s المحرف "." إلى مطابقة سطر جديد وتدفع الراية i النص <body> لمطابقة النص <BODY> (تحسس لحالة الأحرف).

ترجمة -وبتصرف- للفصول Backreferences in Patten وAlternation OR وlookahead and lookbehind وsticky flag y, searching at position من سلسلة The Modern JavaScript Tutorial

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...