النقطة . هي أحد الرموز الخاصة metacharacters في التعبيرات المنتظمة، حيث تقوم بمطابقة أي شيء مهما كان (واحدة فقط من أي شيء إلا إذا استخدمت محدد للكمية Regex quantifier)، ماعدا فواصل الأسطر Line breaks.
مثلاً:
[ien]
Please click here
then go there
[/ien]
عند تطبيق re. على النص السابق تكون نتيجة المطابقة لاشيء، لان أول ظهور لـ re يكون في نهاية السطر في الكلمة here، ثم ينتقل المحرك لمحاولة مطابقة النقطة الموجودة في النمط ولكنه يجد أن بعد re في النص هي نهاية السطر فيفشل المحرك لأن النقطة لا تتطابق مع نهاية السطر. ولكن محركات التعبيرات المنتظمة توفر خياراً Regex modifiers يجعل النقطة قادرة على ضم فواصل الأسطر للمطابقات هذا الخيار يسمى Single line modifier. لتجربة ذلك قم بالدخول إلى regex101 ثم حدد الخيار من القائمة كما في الصورة
لاحظ عند تفعيل الخيار تمت المطابقة مع ظهور تحديد على re.(التحديد على re فقط لأن نهاية السطر شيء غير مرئي)
في الحقيقة هناك ما يسمى بمُعدِلات الوضع Mode modifiers والتي تغير من آلية عمل محرك البحث، والذي يعتبر Single line أحد هذه الأوضاع.
بالنسبة للمحركات الخاصة بجافاسكريبت وفيجوال بيسك فالنقطة لا تقوم نهائياً بمطابقة فواصل الأسطر. ولكن يمكنك التغلب على ذلك عن طريق استخدام أحد فئات الرموز Character sets وهو [\s\S] والذي يقوم بمطابقة أي رمز. حيث تقوم هذه الفئة بمطابقة إما مسافة بيضاء \s (بما في ذلك فواصل الأسطر) ، أو كل ما هول ليس مسافة بيضاء \S . ونظرًا لأن جميع الرموز إما مسافة بيضاء أو ليس مسافة بيضاء ، فإن هذه فئة تتطابق مع أي شيء.
[youtube video=”XIClT9Rahps” width=”480″ height=”420″]
ملحوظة:
لاتقوم جميع المحركات بفهم فواصل الأسطر بنفس الشكل، ولكن أغلبها يتعامل مع \n كفاصل للسطر، بل ومعظم لغات البرمجة لا تفهم غير \n كفاصل للأسطر، ولكن هذا لا يعتبر مشكلة، فمثلاً في ويندوز التي تستخدم فيه \r\n كفاصل للأسطر يتم تحويل \r\n إلى \n تلقائياً أثناء القراءة من ملف ويتم تحويل \n إلى \r\n أثناء الكتابة على ملف.
وهناك بعض المحركات التي توفر خياراً لتحديد أي من الرموز يتم اعتباره كفاصل للسطر، مثل محرك PCRE والذي يتيح لك تحديد إما \r أو \n أو كلاهما \r\n أو أي من الرموز الخاصة بفواصل الأسطر في UNICODE.
[divider height=”30″ line=”1″]
هناك أيضاً \N التي تقوم بمطابقة أي شيء مثل النقطة . ولكن على عكس النقطة فهي لا تتأثر بالخيار Single line. ولكن أيضاً يتوفر لها خيارات للتحكم في الرموز التي يتم اعتبارها فواصل للأسطر.
يجب الحذر عند التعامل مع النقطة، فهي تعمل كالآتي:
- تقوم بمطابقة واحد فقط من أي رمز يقابلها في النص .وتسترجعه كنتيجة مطابقة لتنتقل للرمز التالي في النمط
- ولكن عند استخدام أي من * أو + بعد النقطة سيؤدي هذا إلى أن تقوم بمطابقة كامل النص قبل الإنتقال للرمز التالي في النمط، مما يؤدي إلى ما يسمى بالتتبع الخلفي Regex backtracking وعند سوء التطبيق قد يؤدي ذلك إلى تتبع خلفي كارثي Regex catastrophic backtracking
تخيل معي أنك تريد مطابقة التاريخ الذي يأخذ أي من الأشكال [ien]mm/dd/yy, mm-dd-yy, mm.dd.yy[/ien]، فإن أول شيء ربما يخطر ببالك هو النمط التالي \d\d.\d\d.\d\d وللوهلة الأولى ربما يبدو جيداً فهو يطابق تاريخ بهذا الشكل 02/12/03، لكن أيضاً يطابق هذا 02512703 حيث أن أول نقطة في النمط ستتطابق مع 5 والثانية مع 7.
وبالتالي نحتاج لنمط أفضل، وهنا يكون الأفضل هو استبدال النقطة بفئة من الرموز Character set هكذا \d\d[- /.]\d\d[- /.]\d\d والتي ستقوم بمطابقة التواريخ التي تستخدم أي من – /.
[highlight background=”” color=””]يجب التنويه إلى أن النقطة لاتعتبر رمز خاص Metacharacter داخل فئمة الرموز Character set وبالتالي لايشترط تخطيها [/highlight]
وبالتالي النمط السابق سيقوم بمطابقة التاريخ 19/39/99 ولكن فعلياً هذا لا يعتبر تاريخ صحيح فلايوجد شهر بالرقم 39، وهنا يحتا النمط لبعض التعديلات والتي تحتاج بعض التقنيات التي نتحدث عنها في مواضيع منفصلة. ولكن الهدف هو توضيح متى يكون استخدام النقطة ليس هو الحل الأمثل.
كيفية عمل النقطة في التعبير المنتظم
النقطة كما ذكرنا يمكنها مطابقة أي شيء (أي شيء وليس كل شيء ولكن استخدام بعض أنواع محددات الكميات يجعلها تطابق كل شيء)، وبما أن المحرك يقارن النمط بكل عنصر/حرف/رمز في النص، فعندما يكون النمط هو فقط النقطة هكذا /./ وليكن النص هو I am Mohammad مع عدم تفعيل الخيار global يبدأ المحرك بالنظر لأول حرف I فيطابقه ويسترجعها كعملية مطابقة ثم ينتهي من مطابقة النقط ويسترجع نتيجة واحدة، إما عند تفعيل الخيار global يبدأ المحرك بالنظر لأول حرف I فيطابقه ويسترجعها كعملية مطابقة ثم يجد أن مازال هناك بقية في النص فيبدأ بتطبيق النمط على المسافة البيضاء ويسترجعها كعملية مطابقة فيطابقه ثم a فيطابقه هكذا وبالتالي عند اختبار الكود التالي:
<?php
$str = 'I am Mohammad';
preg_match_all('/./',$str,$m);
echo '<pre>';
var_dump($m);
echo '</pre>';
?>
تكون النتيجة:
array(1) {
[0]=>
array(13) {
[0]=>
string(1) "I"
[1]=>
string(1) " "
[2]=>
string(1) "a"
[3]=>
string(1) "m"
[4]=>
string(1) " "
[5]=>
string(1) "M"
[6]=>
string(1) "o"
[7]=>
string(1) "h"
[8]=>
string(1) "a"
[9]=>
string(1) "m"
[10]=>
string(1) "m"
[11]=>
string(1) "a"
[12]=>
string(1) "d"
}
}
مرة أخرى لا يجب أن تفهم أن . تعني مابقة كل شيء، فهي مثلها مثل أي رمز خاص Metacharacter تقوم بجلب نتيجة واحدة لكل عملية مطابقة، وبالتالي إذا أردت أن تجعلها تطابق كل شيء فعليك استخدام محددات الكميات Quantifiers * أو +
ولكن عليك الحذر أيضاً عند استخدام هذه المحدادت، لأنها ربما تأتي بنتائج غير متوقعة إن لم تستخدمها بشكل غير صحيح، نشرح هذه التحذيرات في الموضوع الخاص بالتتبع الخلفي Regex Backtracking