سنغطي في هذا الفصل التوابع المتنوعة التي تعمل مع التعابير النمطية بشيء من التفصيل بعد أن غطينا موضوع التعابير النمطية تغطية شاملة بدءًا من مقال أساسيات التعابير النمطية وحتى مقال كتابة تعابير نمطية متقدمة (إن لم تتطلع عليها، فننصحك بالرجوع إليها أولًا).
التابع (str.match(regexp
يبحث هذا التابع عن تطابقات للتعبير regexp
في النص str
، وله ثلاثة أنماط:
النمط الأول، الراية g
غير مفعّلة: يعيد التابع التطابق الأول ضمن مصفوفة، تحوي مجموعات ملتقطةً capturing groups وخصائص، هي موقع التطابق index
، والنص الذي نبحث فيه input
، وهو النص str
.
let str = "I love JavaScript"; let result = str.match(/Java(Script)/); alert( result[0] ); // JavaScript (تطابق كامل) alert( result[1] ); // Script (المجموعة الملتقطة الأولى) alert( result.length ); // 2 // Additional information: alert( result.index ); // 7 (موقع التطابق) alert( result.input ); // I love JavaScript (النص الأصلي)
النمط الثاني، الراية g
مفعلة: سيعيد التابع مصفوفةً تضم كل التطابقات الموجودة في صيغة قيم نصية، دون مجموعات ملتقطة، أو غيرها من التفاصيل.
let str = "I love JavaScript"; let result = str.match(/Java(Script)/g); alert( result[0] ); // JavaScript alert( result.length ); // 1
النمط الثالث، إذا لم يوجد تطابق فسيعيد التابع القيمة null
، سواء استخدمنا الراية g
أم لم نستخدمها، انتبه جيدًا إلى أنه لا يعيد مصفوفةً فارغةً عندما لا يجد تطابقات، بل يعيد القيمة null
:
let str = "I love JavaScript"; let result = str.match(/HTML/); alert(result); // null alert(result.length); // Error
إذا أردنا الحصول على النتيجة في مصفوفة، فيمكننا كتابة الشيفرة على الشكل:
let result = str.match(regexp) || [];
التابع (str.matchAll(regexp
اقتباسإضافة جديدة إلى اللغة، وقد تحتاج المتصفحات القديمة إلى شيفرات موائمة polyfills لتعويض نقص الدعم فيها.
يمثل التابع نسخةً محدثةً ومطورةً عن التابع str.match
، ويستخدَم لإيجاد جميع التطابقات وفق المجموعات المحددة، ويختلف عن التابع str.match
في ثلاثة أمور، هي:
-
لا يعيد مصفوفةً بل كائنًا قابلًا للتكرار iterable object، ويمكن إنشاء مصفوفة نظامية منه باستخدام
Array.from
. -
عند استخدام الراية
g
يعيد كل تطابق في مصفوفة تحتوي مجموعات. -
عندما لا يجد تطابقات فلا يعيد
null
، بل كائنًا فارغًا قابلًا للتكرار.
أمثلة عن استخدامه:
let str = '<h1>Hello, world!</h1>'; let regexp = /<(.*?)>/g; let matchAll = str.matchAll(regexp); alert(matchAll); // ليس مصفوفة بل كائن matchAll = Array.from(matchAll); // الآن مصفوفة let firstMatch = matchAll[0]; alert( firstMatch[0] ); // <h1> alert( firstMatch[1] ); // h1 alert( firstMatch.index ); // 0 alert( firstMatch.input ); // <h1>Hello, world!</h1>
إذا استخدمنا الحلقة for..of
للحصول على تطابقات matchAll
، فلن نحتاج إلى تحويل الكائن إلى مصفوفة من خلال Array.from
.
التابع (str.split(regexp|substr, limit
يقسم النص وفقًا لتعبير نمطي (أو نص فرعي)، ويمكن استخدامه دون نص بالشكل التالي:
alert('12-34-56'.split('-')) // array of ['12', '34', '56']
كما يمكن التقسيم وفقًا لتعبير نمطي بنفس الأسلوب:
alert('12, 34, 56'.split(/,\s*/)) // array of ['12', '34', '56']
التابع (str.search(regexp
يعيد التابع موقع التطابق الأول، أو يعيد 1-
إذا لم يجد تطابقًا:
let str = "A drop of ink may make a million think"; alert( str.search( /ink/i ) ); // 10 (first match position)
المحدودية الأكبر للتابع هي إيجاده لأول تطابق فقط. فإذا أردنا مواقع بقية التطابقات فلا بدّ من استخدام طرق أخرى، مثل البحث عن جميع التطابقات باستخدام matchAll
.
التابع (str.replace(str|regexp, str|func
وهو التابع الأساسي للبحث والاستبدال، والأكثر فائدةً، ويمكن استخدامه للبحث عن أجزاء من النص دون الحاجة لتعابير نمطية.
// بدل الشرطة القصيرة بنقطتين متعامدين alert('12-34-56'.replace("-", ":")) // 12:34-56
مع ذلك قد يصعب استخدامه أحيانًا.
عندما يكون المعامل الأول replace
نصًا فسيستبدل التطابق الأول فقط حيث ستلاحظ في المثال الأول استبدال الشرطة القصيرة الأولى فقط بالنقطتين المتعامدتين، ولإيجاد بقية التطابقات واستبدالها، لا بدّ من استخدام التعبير النمطي g/-/
بدلًا من النص "-"
، مع التفعيل الإجباري للراية g
.
// بدل كل شرطة قصيرة بنقطتين متعامدين alert( '12-34-56'.replace( /-/g, ":" ) ) // 12:34:56
يمكن استخدام محارف خاصة في الوسيط الثاني كونه النص البديل.
الرمز | ما يفعله ضمن النص replacment |
---|---|
&$
|
يمثل نص التطابق |
`$
|
يمثل النص الواقع قبل نص التطابق. |
'$
|
يمثل النص الواقع بعد نص التطابق. |
n$
|
يمثل التطابق ذا الرقم n من مجموعة التطابق (الموجود ضمن قوسي تجميع"()" ) وسنتعرف لاحقًا عليها في فصل: المجموعات الملتقطة. |
$<name>
|
يمثل التطابق ذا الاسم name من مجموعة التطابق (الموجود ضمن قوسي تجميع"()" )، وسنتعرف لاحقًا ععليها في فصل: المجموعات الملتقطة. |
$$
|
يمثل المحرف $
|
إليك مثالًا:
let str = "John Smith"; // swap first and last name alert(str.replace(/(john) (smith)/i, '$2, $1')) // Smith, John
يمكن أن نمرر دالةً ضمن الوسيط الثاني، إذا كانت الحالة تتطلب ذلك. وستُستدعى هذه الدالة عند كل تطابق، وستصبح القيمة التي تُعيدها بمثابة النص البديل، أما شكل الدالة فهو:
func(match, p1, p2, ..., pn, offset, input, groups)
حيث:
-
match
: التطابق. -
p1, p2, ..., pn
: محتويات المجموعات الملتقطة، إن وجدت. -
offset
: موقع التطابق. -
input
: النص الأصلي. -
groups
: كائن يضم المجموعات المُسمّاة.
إذا لم توجد أقواس ضمن التعبير النمطي فسيكون لدينا ثلاثة وسطاء فقط (func(str, offset, input
.
وسنعرض بعض الأمثلة:
- تحويل التطابقات إلى أحرف كبيرة:
let str = "html and css"; let result = str.replace(/html|css/gi, str => str.toUpperCase()); alert(result); // HTML and CSS
- استبدال كل تطابق بموقعه في النص:
alert("Ho-Ho-ho".replace(/ho/gi, (match, offset) => offset)); // 0-3-6
- في المثال التالي، ستجد قوسين مفتوحين في التعبير النمطي وبالتالي ستقبل الدالة خمسة وسطاء، الأول للتطابق بأكمله، ثم محتوى القوسين، وبعدهما (لم يستخدما في مثالنا) موقع التطابق والنص الأصلي:
let str = "John Smith"; let result = str.replace(/(\w+) (\w+)/, (match, name, surname) => `${surname}, ${name}`); alert(result); // Smith, John
يفضل استخدام التفكيك destruction عند وجود مجموعات عدة:
let str = "John Smith"; let result = str.replace(/(\w+) (\w+)/, (...match) => `${match[2]}, ${match[1]}`); alert(result); // Smith, John
عندما نستخدم المجموعات المسماة، فسيكون الكائن groups
مع المجموعات دائمًا في الموقع الأخير، وبالتالي سنحصل عليها بالشكل التالي:
let str = "John Smith"; let result = str.replace(/(?<name>\w+) (?<surname>\w+)/, (...match) => { let groups = match.pop(); return `${groups.surname}, ${groups.name}`; }); alert(result); // Smith, John
سيتيح لنا استخدام دالة عند استبدال النصوص قدرةً كبيرةً، لأنها ستزودنا بكل المعلومات عن التطابق، ولها القدرة على الوصول إلى المتغيرات الخارجية، وتنفيذ أي شيء نريده.
التابع (str.replaceAll(str|regexp, str|func
وله وظيفة التابع str.replace نفسها، مع وجود اختلافين رئيسيين:
-
سيستبدل كل التطابقات الموجودة إذا كان وسيطه الأول نصًا، بينما يستبدل
replace
التطابق الأول فقط. -
يعمل تمامًا مثل التابع
replace
إذا كان وسيطه الأول تعبيرًا نمطيًا والرايةg
مفعلةً، ويعطي خطأً إذا لم تكن كذلك.
ويستخدم بشكل رئيسي عندما نريد استبدال جميع التطابقات، وإليك مثالًا:
// استبدل كل الشرطات بنقطتين عموديتين alert('12-34-56'.replaceAll("-", ":")) // 12:34:56
التابع (regexp.exec(str
يعيد هذا التابع تطابقًا مع نمط إذا وجده ضمن النص، وعلى خلاف التوابع السابقة سيُستدعى من قبل كائن تعبير نمطي regexp
وليس من قبل نص str
، ويسلك سلوكًا مختلفًا عند تفعيل الراية g
أو عدم تفعيلها، فإذا لم تكن هذه الراية مفعلةً فسيعيد التطابق الأول فقط، تمامًا مثل التابع (str.match(regexp
، ولن يقدم هذا السلوك أي جديد، ولكن مع وجود الراية g
:
-
سيعيد التطابق الأول، ويخزن الموقع الذي يلي التطابق مباشرةً ضمن الخاصية
regexp.lastIndex
. -
عندما يُستدعى مجددًا سيبدأ البحث انطلاقًا من الموقع المُخزّّن ضمن الخاصية
regexp.lastIndex
معيدًا التطابق التالي، وسيخزن الموقع الذي يليه مباشرةً ضمن الخاصيةregexp.lastIndex
محدّثًا قيمتها. - وهكذا يستمر العمل.
-
إذا لم يجد تطابقات فسيعيد القيمة
null
، ويسند القيمة0
إلى الخاصيةregexp.lastIndex
.
سيعيد الاستدعاء المتكرر لهذا التابع كل التطابقات كما شرحنا في الخطوات السابقة، بينما قبل وجود هذا التابع، كان لا بدّ من استخدام الحلقات للحصول على التطابقات جميعها:
let str = 'More about JavaScript at https://javascript.info'; let regexp = /javascript/ig; let result; while (result = regexp.exec(str)) { alert( `Found ${result[0]} at position ${result.index}` ); // Found JavaScript at position 11, then // Found javascript at position 33 }
سيعمل الأسلوب المتبع في المثال السابق أيضًا، على الرغم من أنّ استخدام التابع str.matchAll
سيناسب المتصفحات الحديثة أكثر.
يمكن استخدام التابع (regexp.exec(str
للبحث انطلاقًا من موقع محدد بضبط قيمة الخاصية regexp.lastIndex
يدويًا. وإليك مثالًا:
let str = 'Hello, world!'; let regexp = /\w+/g; //lastIndex يتجاهل المحرك قيمة الخاصية "g" دون الراية regexp.lastIndex = 5; // البحث انطلاقًا من الموقع 5 alert( regexp.exec(str) ); // world
يفرض وجود الراية y
البحث في الموقع المحدد ضمن الخاصية regexp.lastIndex
تمامًا، وليس بعده.
لنستبدل الراية y
بالراية g
في المثال السابق، وسنلاحظ عدم وجود تطابقات:
let str = 'Hello, world!'; let regexp = /\w+/y; regexp.lastIndex = 5; // search exactly at position 5 alert( regexp.exec(str) ); // null
يناسب هذا الأمر الحالات التي نحتاج فيها إلى قراءة شيء ما من نص باستخدام التعابير النمطية انطلاقًا من موقع محدد تمامًا.
التابع (regexp.test(str
يتأكد هذا التابع من وجود تطابق، ويعيد إحدى القيمتين true/false
، وإليك مثالًا:
let str = "I love JavaScript"; // ينفذ الاختباران التاليان العمل نفسه alert( /love/i.test(str) ); // true alert( str.search(/love/i) != -1 ); // true
مثال مع جواب سلبي:
let str = "Bla-bla-bla"; alert( /love/i.test(str) ); // false alert( str.search(/love/i) != -1 ); // false
في الحالة التي نفعل فيها الراية g
، سيبحث التابع عن الخاصية regexp.lastIndex
ويحدّث قيمتها، تمامًا مثل التابع regexp.exec
، لذلك يمكن استخدامه للبحث في موقع محدد:
let regexp = /love/gi; let str = "I love JavaScript"; // يبدأ البحث من الموقع 10 regexp.lastIndex = 10; alert( regexp.test(str) ); // false (لا تطابق)
لاحظ أنه قد يخفق الاختبار المستمر لتعبير نمطي عام على نصوص مختلفة، لأن التابع regexp.exec
يستدعي قيمًا متقدمةً للخاصية regexp.lastIndex
، وبالتالي قد يبدأ البحث في نص آخر ابتداءً من موقع مختلف عن الصفر.
لاحظ في هذا المثال كيف سنختبر النص ذاته مرتين متتاليتين، وسيخفق الاختبار الثاني:
let regexp = /javascript/g; // (regexp just created: regexp.lastIndex=0) alert( regexp.test("javascript") ); // true (regexp.lastIndex=10 now) alert( regexp.test("javascript") ); // false
للالتفاف على هذه المشكلة، يمكننا ضبط قيمة الخاصية regexp.lastIndex
على الصفر قبل البدء بكل بحث، أو استخدام توابع النصوص، مثل .../str.match/search
، بدلًا من توابع التعابير النمطية، فهي لا تستخدم الخاصية lastIndex
.
ترجمة -وبتصرف- للفصل Methods of RegExp and string من سلسلة The Modern JavaScript Tutorial.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.