[highlight background=”” color=””]لاحظ قمنا بتخطي قمنا بتخطي / لأنها مستخدمة كمحدد للنمط، أيو يمكنك استخدام محدد آخر حتى تتلاشي تخطي علامة / خاصة إن كانت وجودة بكثرة في النص مثل الروابط. وفي حالتنا الآن كان يمكن كتابة النمط هكذا %[/0-9]% [/highlight]
مثال:
لفهم كيفية عمل فئات الرموز، سنقوم بالبحث في النص التالي عن كل ما هو <a> (طبعاً لاتكتب الروابط هكذا ولكن لخدمة الشرح فقط)
If you have an account,<a>login</a>, Or if not, please <a>register</a>بالتأكيد ليس أمراً صعباً ويمكن حتى بدون استخدام Character Set هكذا
<?php $x = 'If you have an account,<a>login</a>, Or if not, please <a>register</a>'; preg_match_all("/<a>/",$x,$m); echo '<pre01/>'; var_dump($m); echo '</pre>'; ?>هذا لأننا نعرف مالذي نبحث عنه وهو شيء واحد <a>، لكن إذا إردنا أن نبحث عن كل ما هو تاج html في وثيقة ما، كيف نفعلها؟
كما نعلم أن كل تاج له اسم يختلف عن الآخر، وبالتالي نحتاج لشيء يقوم بالتالي:
أولا يقوم بالبحث عن علامة > وهي فاتحة التاج ثم يقوم بالبحث عن أي تسلسل مكون من أي حرف من الأحرف الصغير ثم البحث عن علامة < وهي غالقة التاجوعند تحويل هذه القواعد إلى نمط تصبح بهذا الشكل /<[a-z]>/ ، إذن علينا اختبار ذلك وفحص النتيجة، ولكن سنقوم بالتعديل قليلاً على الجملة السابقة بإضافة تاج <p> للتأكد من أنها ستجلب أكثر من وسم مختلف
<?php $x = '<p>If you have an account,<a>login</a>, Or if not, please <a>register</a></p>'; preg_match_all("/<[a-z]>/",$x,$m); echo '<pre01/>'; var_dump($m); echo '</pre>'; ?>فتكون النتيجة:
array(1) { [0]=> array(3) { [0]=> string(3) "<p>" [1]=> string(3) "<a>" [2]=> string(3) "<a>" } }ماذا يحدث إذا قمنا بإضافة <div> إلى النص؟
إذا قمنا بإضافة <div> فلن تعتبر مطابقة في الكود السابق، لأن المحرك سيقوم بعمل الآتي:
عند الوصول للعلامة > في <div> سيعتبرها المحرك مطابقة ثم ينتقل لتنفيذ الـ Character Set التي تقوم باسترجاع حرف واحد فقط بين a و z وهو حرف d فينتهي المحرك من Character Set (لا تنس أن Character Set تقوم بمطابقة حرف واحد فقط) ثم ينتقل إلى العلامة < ويجدها مطابقة فتصبح النتيجة النهائية للبحث هي <d> وبالتالي لايوجد في النص الذي نبحث داخله شيء من هذا القبيل فلايتم اعتبار <div> نتيجة مطابقة للنمط.إذن علينا التفكير في شيء يجعل المحرك لايقوم بجلب حرف واحد فقط، وهنا نحتاج لتكرار Character Set قبل الإنتقال إلى الرمز التالي وهنا يأتي دور محددات الكميات Regex quantifiers وتحديداً * أو + . لذا علينا التعديل ليصبح النمط هكذا في الكود
<?php $x = '<div><p>If you have an account,<a>login</a>, Or if not, please <a>register</a></p></div>'; preg_match_all("/<[a-z]*>/",$x,$m); echo '<pre01/>'; var_dump($m); echo '</pre>'; ?>تكون النتيجة:
array(1) { [0]=> array(4) { [0]=> string(5) "<div>" [1]=> string(3) "<p>" [2]=> string(3) "<a>" [3]=> string(3) "<a>" } }ماذا عن ما إذا كنت تريد البحث الوسم كاملاً أي مضافاً إليه وسم الغلق وأيضاً النص بينهما مثل <a>login<a/> ؟
للوصول إلى ذلك نحتاج بعض التقنيات مثل التجميع الإلتقاطي Capturing groups والمراجع Regex Backreferences، وسنقوم بذلك في المواضيع الخاصة بها.
فئات الرموز الملغية Negated Character Classes فئات الرموز الملغية الهدف منها هو تحديد مجموعة من الأحرف/الرموز التي تريد مطابقة ما دونها، أو بمعنى آخر مطابقة أي شيء آخر ما دام غير موجود ضمن هذه الفئة. يتم كتابة فئات الرموز الملغية بكتابة ^ بعد القوس المربع الأول هكذا [^a-z] ، أي إبحث عن كل ما هو ليس من الأحرف a حتى z الصغيرة. بشكل عام تقوم الـ Character class بمطابقة قواسم/فواصل الأسطر \r\n ولكن يمكننا إلغاء ذلك بإضافة قواسم/فواصل الأسطر إلى فئة الرموز الملغية هكذا [^a-z\r\n] فئات الرموز الملغية لا تعتبر أن هناك مطابقة إذا كان ماتبحث عنه آخر السطر، فمثلاً في النص التالي xexcx إذا كنت تريد مطابقة كل x وما بعدها إذا كان مابعدها ليس حرف e ، فإن: xe لا تعتبر مطابقة لأن بعد x يوجد e xc تكون مطابقة لأن x ليس بعدها e ولكن x في نهاية السطر بالرغم من أن ما بعدها ليس e ولكن لاتعتبر مطابقة. لاحظ نتيجة الكود التالي <?php $str = 'xexcx'; preg_match_all('/x[^e]/',$str,$matchings); echo '<pre>'; var_dump($matchings); echo '</pre>'; ?>تكون النتيجة
array(1) { [0]=> array(1) { [0]=> string(2) "xc" } } لكن تعتبر x التي بعده مسافة مطابقة في هذا النص x xe. أما إذا أردنا مطابقة كل ما هو x وما بعدها إن كان ما بعدها ليس حرف e بغض النظر عن ما إذا كان في نهاية السطر أم لا فيمكننا استخدام ما يسمى الفحص الأمامي السلبي Negative lookahead (نتحدث عنه في موضوع منفصل)، وإذا كنت فضولياً فيمكنك استخدام النمط التالي x(?!e) هل تتذكر أن الإرتكازات هي مجرد أماكن ولا تجعل المحرك يتقدم في النص؟ إذا كنت كذلك فيجب أن تعلم أن هذا يطلق عليه تطابق صفري الطول Zero-length assertion (أي أن المحرك تحرك مسافة صفر في النص، أي عملياً لم يتحرك من موضعه في النمط)،ولكن فئات الرموز الملغية ليست صفرية الطول Zero-length assertion، أي أن المحرك يتقدم خطوة لأمام في النص بعد خروجه من هذه الفئة. الرموز الخاصة داخل فئات الرموزهناك بعض الرموز الخاصة التي تستخدم داخل فئات الرموز ويكون لها معنى مثل القوس المربع بالتأكيد ] وعلامة ^ والشرطة المائلة للخلف \ وأيضاً الشرطة العادية – ، وبالتالي أي من هذه الرموز إذا أردت استخدامها كرمز حرفي وليس كجزء من النمط فعليك تخطيها بالشرطة المائلة للخلف \ . أما باقي الرموز الخاصة بالتعبير المنتظم فيمكن استخدامها داخل الرمز المجمع بدون تخطي.
مثلا [\\x] تقوم بمطابقة \ أو x، وقد قمنا بتخطي \ بأخرى لأنها جزء من تكوين فئة الرموز Character set. وإذا أردت استخدام العلامة ^ كحرف ليس له معنى داخل فئات الرموز، فعليك وضعه في أي مكان غير الذي يلى علامة {. أما عن استخدام القوس المربع المخصص للقفل ] داخل فئات الرموز، فيمكن ذلك إما بوضعه مباشرة بعد القوس المربع الفاتح [ هكذا []x] أو إذا كانت رموز مجمعة ملغية فيتم وضعها بعد علامة ^ هكذا [^]x][highlight background=”” color=””]هذا لا يعمل في جافاسكريبت بنفس المنطق، حيث تعتبر [] فئة رموز فارغة والتي دائماً ما تفشل في المطابقة. وكذلك [^] والتي تعتبر فئة رموز ملغية فارغة والتي تقوم بمطابقة أي حرف/رمز[/highlight]
فيما يخص الشرطة العادية عند استخدامها داخل فئات الرموز كحرف فيمكن وضعها إما بعد القوس المربع الفاتح [ مباشرة هكذا [-x] أو قبل القوس المربع الغالق هكذا [x-]. ولكن عند استخدام الشرطة العادية داخل فئات الرموز الملغية Negated Character class فيتم وضعها إما بعد علامة ^ مباشرة [^-x] أو قبل القوس المربع الغالق هكذا [^x-] .[highlight background=”” color=””]كن حذراً عند استخدام الشرطة العادية داخل فئات الرموز، لأن استخدامها في أي مكان آخر (أي مكان ليس عبارة عن نطاق مثل [a-z]) ربما يتم معاملتها كحرف عادي أو كخطأ، فليس جميع المحركات تفهمها بنفس الطريقة وأسلوب التعامل معها يختلف من محرك لآخر.[/highlight]
تكرار فئات الرموزعند تكرار فئات الرموز باستخدام محددات الكميات Regex quantifiers مثل * أو + أو ? ، فأنت تقوم بتكرار فئات الرموز وليس نتيجة المطابقة. فمثلا عند تطبيق النطاق [0-9] على الرقم 839 فإن النتيجة الطبيعية لعملية المطابقة الأولى تكون 8 أليس كذلك؟
ماذا إذا أضفنا النجمة * إلى النطاق هكذ [0-9]* ؟
ربما تتوقع أنه بما أن نتيجة أول عملية مطابقة هي 8 فسيقوم المحرك ببدء تكرار عملية المطابقة للرقم 8 بما أنه نتيجة مطابقة للفئة التي نريد تكرارها، ولكن ما يحدث هو عملية تكرار للفئة نفسها، بحيث يأخذ المحرك الخطوات التالية
تكون أول نتيجة للمطابقة هي 8، يعرف المحرك أنه يحتاج لتكرار الفئة ينتقل المحرك للرقم التالي في 839 وهو 3 هل 3 يقع بين الأرقام من 0 إلى 9 ؟ نعم. تصبح المطابقة حتى الآن هي 83 ينتقل المحرك للرقم التالي في 839 وهو 9 هل 9 يقع بين الأرقام من 0 إلى 9 ؟ نعم. تصبح المطابقة حتى الآن هي 839. قم باختبار الكود التالي <?php $str = '839'; preg_match_all('/[0-9]/',$str,$m); preg_match_all('/[0-9]*/',$str,$n); echo '<pre>'; var_dump($m); var_dump($n); echo '</pre>'; ?>أما إذا أردت تكرار النتيجة نفسها وهي 8 يمكنك الإعتماد على ما يسمى بالمجموعات الإلتقاطية (نتحدث عنها في موضوع منفصل) حيث توفر هذه المجموعات ما يسمى بالمرجع الخلفي backreference ولن نخوض في تفاصيلها ولكن فيها يتم إحاطة جزء من النمط بالأقواس العادية () وهذا الجزء نعرف أنه سيتكرر في النص، هنا كل ما هو محاط داخل الأقواس العادية يأخذ رقم، هذا الرقم يمكننا استخدامه في النمط للإشارة إلى أن نتيجة مطابقة الجزء المحاط بالأقواس ستتكرر مرة أخرى، وبالتالي يمكننا الحصول على نتيجة المطابقة وتكرارها هكذا ([0-9])\1+، لا أطلب منك فهمه حالياً ولكن هذا يعني أننا نطلب من المحرك البحث عن رقم من 0 حتى 9 هذا الرقم متبوعا بنفسه. وبالتالي عند تطبيقه على الرقم 333 ستحدث مطابقة ولكن ليس مع 896 لأن المحرك عندما يطابق أول رقم 8 سيبحث عن 8 أخرى تليه فلن يجد ثم ينتقل إلى 9 فلن يجد ثم إلى 6 فلا يد مطابقات. لكن هذا النمط يحصل على تطابق داخل الرقم 8996 حيث أن هناك رقمان مكرران وهما 99.

