<?php $str = 'This a word1'; echo preg_replace('/w\w+[0-9]/','$0'.' '.'reference',$str); ?>
حيث قمنا:
- تعريف النمط البسيط وهو /w\w+[0-9]/
- عندما يبدأ المحرك في البحث عن النمط داخل النص سيفشل حتى يجد الكلمة word1
- عند الحصول على مطابقة كامل النمط سيتم تخزين نتيجة المطابقة في المرجع 0
- بما أننا نعرف أن نتيجة مطابقة كامل النمط (والتي هي word1) يتم تخزينه في المرجع 0، قمنا بعمل احلال للكلمة word1 عن طريق استدعاء المرجع 0$ مع عمل دمج مع الكلمة reference. فتكون النتيجة هي [ien]This is word1 reference[/ien].
وهذا هو الوضع الإفتراضي، بحيث يقوم المحرك بتخزين ما تم مطابقته داخل المرجع backreference. وهنا يمكننا الاستفادة من هذه التقنية في عمليات البحث المعقدة، بحيث يمكننا تقسيم النمط نفسه إلى مجموعات بحيث يقوم المحرك بالإشارة إلى كل مجموعة بمرجع خاص (رقم خاص في الذاكرة). عن طريق وضع كل مجموعة من الرموز Atoms داخل الأقواس ( ) ، وكل مجموعة سيتم الإشارة إليها بمرجع رقمي بنفس تسلسل وجودها. لاحظ الصورة التالية
- كامل النمط يأخذ المرجع 0
- المجموعة الأولى في المربع الأخضر تأخذ المرجع 1
- المجموعة الثانية في المربع البنفسجى تأخذ المرجع 2
مع العلم أنه يتم الترقيم من الخارج للداخل، بحيث إذا كان هناك نمط بالشكل (((rat )bat )cat )dog وذلك في حالة المجموعات المتداخلة، فإن ترقيم المجموعات يكون كما هو موضح بالصورة
تسمى عملية تخزين نتائج مطابقات المجموعات في المراجع بالإلتقاط، لذلك تسمع التجميع الإلتقاطي أو المجموعات الإلتقاطية، وفي الحقيقة المجموعات الإلتقاطية تعتبر من أهم عناصر القوة للتعبيرات المنتظمة، فمن خلالها تستطيع عمل أمور كثيراً تبدو ظاهرياً معقدة لكن مع استخدام المجموعات تكون أسهل ما يمكن.
مثال 1:
هل تتذكر المثال في الموضوع الخاص بفئات الرموز Character sets والذي حاولنا فيه البحث عن وسوم الفتح في النص التالي:
<div><p>If you have an account,<a>login</a>, Or if not, please <a>register</a></p></div>
حيث قمنا بتطبيق النمط <[a-z]*>، ولكن سألنا السؤال التالي:
ماذا عن ما إذا كنت تريد البحث عن الوسم كاملاً أي مضافاً إليه وسم الغلق وأيضاً النص بينهما مثل <a>login<a/>؟
وكان حينها الأمر يصعب تنفيذه لأننا لم نكن نعرف عن التجميع الإلتقاطي، ولكن الآن نستطيع عمل ذلك بكل بساطة. وبالتالي إذا قمنا بإمعان النظر في النص فيمكننا أن نحلل ما نريد مطابقته كالآتي:
- نحتاج شيء يبحث عن <
- ثم نبحث عن حرف أو أكثر من الأحرف الصغيرة وليكن سيتم مطابقة div
- ثم نبحث عن >
- إلى هنا نكون قد طابقنا وسم الفتح
- ثم نحتاج لجلب أي شيء بعده حتى نصل لوسم الغلق
- نحتاج شيء يبحث عن <
- ثم البحث عن /
- ثم نبحث عن حرف أو أكثر من الأحرف الصغيرة ولكن ليس أي حرف، بل يجب أن تكون div
- ثم نبحث عن >
بعد تحليل المطلوب تبين أننا نحتاج للبحث عن شيء وهو div ثم يجب أن تظهر div مرة أخرى في النص (في وسم الغلق)، وبالتالي نستطيع تخزين div في مرجع لإعادة استخدامها. وهنا نحتاج لإحاطة الجزء من النمط الذي سيقوم بمطابقة div بأقواس المجموعة، فيكون النمط كالآتي <([a-z]*)>.*<\/\1> فتكون نتيجة المطابقة كالتالي
في النمط السابق قمنا باستخدام مرجع المجموعة ([a-z]*) لتحديد النص الموجود في وسم الفتح لأنه هو نفسه النص في وسم الغلق وبما أن كامل النمط يأخذ المرجع 0 فإن المرجع الخاص بهذه المجموعة هو 1 والذي قمنا باستخدامه في وسم الغلق هكذا <\/\1>. وبالتالي تم مطابقة الوسم div بما في ذلك النص داخل الوسم.
ولاحظ ما قامت به النقطة . والتي قامت بمطابقة أي شيء بين وسمي الفتح والغلق عن طريق إضافة * والتي تجعل النقطة تقوم بمطابقة أي شيء صفر مرة أو أكثر.
[highlight background=”” color=””]يجب الإنتباه أن .* لم تقم فقط بمطابقة مابين الوسمين وإنما قامت بمطابقة باقي النص كاملاً مما أدى إلى فشل باقي النمط فحدث تتبع خلفي Regex backtracking حتى تتم مطابقة باقي رموز النمط[/highlight]
مثال 2:
ولكي تصبح الرؤية أوضح لاحظ النص التالي:
<p>You need [google] (https://www.google.com) to search for information and [facebook] (https://www.facebook.com) for social networking and [twitter] (https://www.twitter.com) also is a great social networking website<p>
النص السابق يحتوي على مجموعة من الروابط داخل أقواس عادية ونصوص خاصة بالروابط داخل أقواس مربعة، وأنت تريد أن تقوم بتحويل كل منها إلى رابط <a></a> هكذا
<a href="https://www.google.com" title="Go to google">google</a> <a href="https://www.facebook.com" title="Go to facebook">facebook</a> <a href="https://www.twitter.com" title="Go to twitter">twitter</a>
علينا تحليل الوضع أولاً:
- نحتاج من المحرك البحث أولاً عن [ ولكن بما أنها جزء من الرموز الخاصة بالمحرك، نحتاج لعمل تخطي لها هكذا \[
- ثم نحتاج لمطابقة أي شيء بعد هذا القوس المربع فنستخدم .* فيصبح النمط هكذا \[.*
- ثم القوس ] فيصبح النمط هكذا \[.*]
- ثم مسافة بيضاء \s فيصبح النمط هكذا \[.*]\s
- ثم قوس عادي للفتح ( ولكن يجب أن لايتم معاملته كقوس مجموعة إلتقاطية لأننا نريد مطابقته حرفياً، فنقوم بتخطيه هكذا \( فيصبح النمط هكذا \[.*]\s\(
- مرأخرى نحتاج لمطابقة كل شيء داخل الأقواس فنضيف .* للنمط ليصبح هكذا \[.*]\s\(.*
- ثم إضافة قوس القفل مع عمل تخطي له ليصبح النمط هكذا \[.*]\s\(.*\)
من المفترض أن يعمل النمط السابق لأننا قمنا بقراءة المطلوب كما ينبغي وحولناه إلى نمط لكن للأسف لن يأتي بالنتيجة المطلوبة، وذلك لأن المحرك سيقوم بالتالي:
- عند وصول المحرك إلى .* الأولى في النمط سيقوم بمطابقة أكبر قدر من النص والذي في حالتنا سيجعل باقي النص بالكامل مطابقة لأننا نعرف أن المحرك عندما بجد * يقوم بجلب أكبر قدر من المتطابقات Greedy Quantifiers،
- بعد مطابقة كامل النص عن طريق .* سينتقل المحرك إلى الرمز التالي في النمط وهو ]
- يفشل المحرك في البحث عن ] لأنه يقف الآن عند نهاية النص فيبدأ بعمل تتبع خلفي Regex Backtracking
- حتى يجد مطابقة مع ] بعد twitter
- ينتقل المحرك للرمز التالي وهو \s فتحدث مطابقة مع المسافة البيضاء بعد القوس مربع
- ينتقل المحرك للرمز التالي وهو ( فتحدث مطابقة مع القوس بعد المسافة البيضاء
- ينتقل المحرك للرمز التالي وهو .* والتي ستقوم بمطابقة باقي النص كاملا مرة أخرى
- فيفشل المحرك في البحث عن القوس الذي يليها، فيبدأ بعمل تتبع خلفي Regex Backtracking
- حتى يصل إلى القوس ) بعد [ien]https://www.twitter.com[/ien]
وبالتالي تكون النتيجة هكذا
ولكننا لانريد أن تقوم .* بمطابقة كامل النص، وإنما نريد مطابقة كل ما هو داخل الأقواس المربعة فقط، فسنقوم بتحويل النجمة * إلى Lazy Quantifiers حتى يطابق أقل قدر ممكن، وذلك باستخدام العلامة ? فيصبح النمط هكذا \[.*?]\s\(.*?\)
عند هذا القدر يمكننا اختبار فقط المطابقة للتأكد من أننا استطعنا قراءة النص بالشكل الذي نريد:(يمكنك اختبار ذلك على regex101)
<?php $str = '<p>You need [google] (https://www.google.com) to search for information and [facebook] (https://www.facebook.com) for social networking and [twitter] (https://www.twitter.com) also is a great social networking website<p>'; preg_match_all('/\[.*?]\s\(.*?\)/',$str,$m); echo '<pre>'; var_dump($m); echo '</pre>'; ?>
تكون النتيجة:
array(1) { [0]=> array(3) { [0]=> string(33) "[google] (https://www.google.com)" [1]=> string(37) "[facebook] (https://www.facebook.com)" [2]=> string(35) "[twitter] (https://www.twitter.com)" } }
وهذا يعني نجاح المطابقة، ولكن الآن نريد أن نحول هذه المطابقات إلى روابط <a>، وهنا يجب علينا تقسيم النمط إلى مجموعات حتى نستطيع الرجوع إلى الرقم المرجعي لكل عملية مطابقة والإستفادة منه.
وبما أننا نريد مرجع للرابط ومرجع للنص الخاص بالرابط فسنقوم بتحويل النمط ليصبح هكذا /\[(.*?)]\s\((.*?)\)/ . بالتالي يمكننا الاستفادة من الرقم المرجعي لكل مجموعة لعمل بحث واستبدال.
يمكنك استخدام موقع regexr لعمل البحث والإستبدال هكذا:
ويجب أيضاً تحديد الخيار global من flags حتى يتم تطبيق النمط على كامل النص هكذا
أو باستخدام الدالة preg_replace في PHP هكذا
reg_replace('/\[(.*?)]\s\((.*?)\)/','<a href="$2" title="Got to $1">$1</a>',$str);
حيث قمنا:
- بوضع الرقم المرجعي $2 داخل href لأنه يشير إلى المجموعة التي قامت بعمل مطابقة للرابط
- قمنا باستخدام المرجع $1 والذي يشير إلى المجموعة الأولي والتي تشير إلى النص بين [] في الأماكن التي نريد ظهور نص الرابط بها وهي title وأيضاً نص الرابط.
أعتقد الآن عرفت أهمية التجميع الإلتقاطي وكيف يمكنك استخدامها في عمليات البحث المعقدة، والآن أطلب منك محاولة استخدام ما تعلمت إلى الآن في البحث عن وسم HTML بما في ذلك السمات HTML attributes مثل <div id=”mydiv” class=””myclass”>any text</div>
الآن كيف يقوم المحرك بمطابقة النمط (\d+)\w+\1 مع النص 123×12 ؟
سأقوم بشرح النمط ولكن يجب ألا تنسى أن المحرك يحاول الإعتماد على أماكن التتبع الخلفي Backtracking لتجربة كل الإحتمالات.
- المحرك يقف قبل المجموعة (\d+) في النمط وقبل الرقم 1 الأول في النص
- يدخل المحرك للمجموعة
- يبدأ في البحث عن رقم أو أكثر بسبب العلامة + فيقوم بمطابقة 123 ويقوم بتخزينها في المرجع \1
- يخرج المحرك من المجموعة فيصبح موقعه قبل \w+ في النمط وقبل x في النص
- تقوم \w+ بمطابقة كل شيء بداية من x وحتى نهاية النص، فهي تقوم بمطابقة كل ما هو تابع لفئة الرموز [A-Za-z0-9_]
- الآن المحرك يقف قبل \1 في النمط وعند نهاية النص (بعد 2 الأخيرة)
- المحرك يعرف أن النمط لم ينتهي بعد ومازالت هناك حاجة لمطابقة \1 التي تحتوي الآن على 123 فيبدأ بعمل backtracking حرف حرف بناءاً على آخر حالة تمت عندها المطابقة وهي في هذه الحالة \w+
- عندما يعود المحرك إلى \w+ يقوم بتقليص المطابقات بمقدار واحد (لربما + كان طماعاً أكثر من اللازم)
- الآن المحرك يقف قبل \1 في النمط و قبل 2 في النص، وبالطبع لايجد مطابقة بين 2 و 123 الموجودة في \1
- فيقوم مرة أخرى بتقليص مطابقات \w+ بمقدار واحد (لربما + كان طماعاً أكثر من اللازم)
- الآن المحرك يقف قبل \1 في النمط و قبل 1 في النص، وبالطبع لايجد مطابقة بين 12 و 123 الموجودة في \1
- يقوم المحرك بتكرار التراجع للخلف ولكن هذه المرة عند رجوعه للخلف يكون موضعه قبل x في النص وهذا المكان هو المكان الذي كان يقف فيه قبل مطابقة \w+ ولكنه اكتشف سابقا أن هذا الموضع لم تتم عنده مطابقة وكما نعرف أن المحرك في التتبع الخلفي يرجع لحالة ما تم تخزينها في ذاكرة المحرك ويجب أن يكون قد حدث عند هذه الحالة مطابقة، وبالتالي يعود المحرك إلى حالة أخرى.
- يعود المحرك للمجموعة (\d+) ويعتبر أنه ربما يكون قد طمع أكثر من اللازم فيجب ان يقلص المطابقة مع الأرقام لتصبح مطابقة المجموعة هي 12 وبالتالي الآن \1 تحتوي على 12
- يخرج المحرك من المجموعة فيصبح موقعه قبل \w+ في النمط وقبل 3 في النص
- تقوم \w+ بمطابقة كل شيء بداية من 3 وحتى نهاية النص
- الآن المحرك يقف قبل \1 في النمط وعند نهاية النص في النص (بعد 2 الأخيرة)
- المحرك يعرف أن النمط لم ينتهي بعد ومازالت هناك حاجة لمطابقة \1 التي تحتوي الآن على 12 فيبدأ بعمل backtracking حرف حرف إلى أن يجد أن هناك أيضاً 12 في نهاية النص فتحدث المطابقة
استخدام المرجع داخل النمط نفسه
قلنا أن عملية إحلال مرجع تتم بكتابة رقم المرجع متبوعاً بعلامة $، لكن عند استخدام المرجع داخل النمط يتم استخدام الشرطة المائلة للخلف \ . لكن مالفائدة من استخدام المرجع داخل النمط نفسه؟
في الحقيقة هذه التقنية مهمة جداً. كيف ذلك؟
لنفرض أنك قمت بكتابة عدد كبير من النصوص، ثم اكتشفت أن هناك كلمات مكررة مثل [ien]He is is a doctor, and he can not fix a car car.[/ien]. وإذا تخيلنا أن هناك مجموعة من الصفحات بها نفس المشكلة،كيف ستقوم بتغييرها دفعة واحدة بحيث تحذف التكرار؟ ليس أمامك خيار غير التعبير المنتظم.
عندما تبدأ في التفكير في الحل تجد أنك تحتاج إلى البحث عن شيء متبوعاً بنفسه أليس كذلك!. وبالتالي يمكننا نكتب نمط يبحث عن التالي:
- يبحث عن كل ماهو حرف كلمة Word Character هكذا /\w+/
- لكن هذا سيقوم بجلب جميع الكلمات في النص، ونحن نحتاج المكرر فقط
- فنقوم بوضع النمط داخل مجموعة هكذا /(\w+)/
- ثم نتيجة البحث متبوعة بمسافة \s
- ثم الكلمة متبوعة بنفسها هكذا (\w+)\s\1
ولكن إذا قمت بتطبيق النمط بهذا الشكل ستحصل على المطابقات التالية:
كما نلاحظ أنه تم تحديد كل مكرران بينهما مسافة، لكن هناك شيء غير متوقع وهو الحرفان n في can not، وبالتالي نحتاج لإضافة حدود الكلمة Word boundaries للنمط ليصبح \b(\w+)\s\1\b . والتالي يمكننا استخدام هذا النمط لعمل احلال لكل مكرر. لذا سنقوم بكتابة الكود التالي
<?php $str = 'He is is a doctor, and he can not fix a car car.'; echo preg_replace('/\b(\w+)\s\1\b/','$1',$str); ?>
لكن هذا النمط سيفي بالغرض فقط إن كانت الكلمة مكررة مرة واحدة فقط (كلمتان متتاليتان) أما إذا كان التكرار أكثر من مرة (الكلمة مكررة أكثر من مرة) مثل [ien]He is is a doctor, and he can not fix a car car car.[/ien] فالكود السابق يحتاج إلى تعديل.
كما لاحظنا في النمط السابق فقد قمنا بالبحث عن التكرار عن طريق \s\1 ، وبالتالي نحتاج من المحرك أن يبحث عن التكرار أكثر من مرة، وهنا يأتي دور محدد الكمية Quantifier وهو + بحيث نطلب من المحرك جلب أقصى عدد من المطابقات. ولكن وضع + مباشرة بعد \1 سيطلب من المحرك مطابقة المرجع 1 فقط مرة أو أكثر ولكن نحتاج للبحث عن المرجع مسبوقاً بمسافة، لذلك سنقوم بجمعهما في مجموعة هكذا (\s1)+ ، فيكون النمط النهائي هو \b(\w+)(\s\1)+\b . قم باختبار النمط على النص السابق ولاحظ الفرق.
المجموعات الغير إلتقاطية Non-capturing groups
كما نعرف أن التجميع تظهر أهميته عند الحاجة للإستفادة من المطابقات لإعادة الإستخدام عن طريق المرجع Backreference، لكن هناك ايضاً تجميع غير إلتقاطي، أي لايلتقط المرجع. ولكن مالفائدة منه إذن إن لم أكن سألتقط المرجع وأستفيد منه؟
لكي تعرف الإجابة على هذا السؤال تخيل معي أنت تريد أن تقوم بالبحث عن أي من cat أو flower متبوعاً بمسافة بيضاء، فتقوم باستخدام التبديل Regex alternation | . ولكن كيف سنكتبها؟
إذا قمت بكتابتها هكذا flower|cat\s فالمحرك سيقوم بالبحث عن flower فقط أو cat بعدها مسافة ولكن نحن نريد أن تكون المسافة بعد أي منهما وليس cat فقط. وبالتالي سنحتاج لوضع عملية التبديل داخل مجموعة هكذا (flower|cat)\s . ولكن بهذا الوضع سيقوم المحرك بالتقاط التطابق وتخزينه في مرجع backreference. وفي نفس الوقت أنت لا تنوي إعادة استخدام المتطابقات وبالتالي انت استهلكت جزء من كفاءة وآداء المحرك لشيء غير مطلوب. إلى جانب أن أغلب محركات البحث تسمح بعدد معين من المراجع لايزيد عن 99 وبالتالي لاتريد هذه الأقواس الموجودة فقط لمجرد تحديد جزء من النمط وليس بهدف إعادة الإستخدام أن ينتقص من عدد المراجع المتاحة لك. وربما لايظهر تأثير ذلك على جملة بسيط مثل هذه ولكن مع ملفات كبيرة تحتاج لأن تخبر المحرك بأن هذه المجموعة ليست بهدف الإلتقاط.
أيضاً مع وجود مجموعات إلتقاطية في نفس النمط سيؤدي ذلك إلى وجود قائمة من المراجع أنت في غنا عنها ولا تحتاجها ضمن مجموعة المراجع المرغوبة. وهنا يأتي دور المجموعات الغير إلتقاطية، بحيث تطلب من المحرك عدم إلتقاط المرجع عن طريق إضافة ?: بعد ( هكذا (?:flower|cat)\s .
المجموعات الإسمية Regex named groups
<?php $test="yet another test"; preg_match_all('/(?P<word>t[^s]+)/',$test,$matches); echo '<pre>'; var_dump($matches['word']); echo '</pre>'; ?>
لاحظ نستطيع الوصول لمطابقات التي تم تخزينها في المرجع word في السطر رقم 5
المرجع الأمامي Forward Reference
معظم المحركات تدعم المرجع الأمامي، بمعنى أنه يمكنك وضع مرجع لمجموعة ما لم تظهر بعد في النمط. فالنمط التالي (\2two|(one))+ يقوم بمطابقة oneonetwo،لمعرفة كيف ذلك لاحظ الصورة التالية
- لدينا مجموعتان، الأولى محاطة بالأخضر والثانية محاطة بالبنفسج
- عندما يبدأ المحرك بالمطابقة، يحاول بمطابقة المرجع \2 والذي لم يصل إليه بعد
- وبالتالي الخيار الأول بالكامل قبل علامة التبديل | لم يحدث مطابقة له
- ينتقل المحرك إلى قبل الحرف o في النمط ويبدأ البحث في النص فتحدث مطابقة إلى أن تحدث مطابقة للخيار الثاني كاملاً مع one في النص.
- بما أن الخيار الثاني يوجد داخل مجموعة يتم تخزين المطابقة في المرجع الخاص بالمجموعة وهو \2
- ينتقل المحرك لعامة + فيعرف أنه لم ينتهي من المطابقة بعد ويحاول البحث أكثر عن متطابقات فيقوم بتكرار المجموعة الأولى
- يقوم المحرك بإعادة المجموعة الأولى بالرجوع قبل \2 في نفس الوقت الذي يقف فيه بعد one الأولى
- عندما يقوم المحرك بالرجوع قبل \2 يجد أن هذا المرجع يحتوي على one فيصبح الخيار الأول في البحث هو onetwo
- وهذا بالضبط ما هو موجود بعد one الأولى وتحدث مطابقة كاملة مع oneonetwo ويكون المرجع \1 يحتوي على onetwo ويكون المرجع \2 يحتوي على one.
إستثناء:
معظم المحركات الحديثة تدعم المرجع الأمامي لكن هذا لا ينطبق على بعض المحركات مثل الجافاسكريبت والتي لاتفهم المرجع الأمامي ولكن لاتظهر أخطاء وإنما يحدث ما يسمى بالمطابقة الصفرية Zero-length match أي مطابقة الطول (نتحدث عنها في موضوع خاص). وبما أن هذا لافائدة منه اعتبرته بعض محركات التعبير المنتظم التي لاتدعم المرجع الأمامي كخطأ مثل بايثون وفيجوال بيسك وغيرها.
المرجع المتداخل Nested Reference
وهذا عندما يتم استخدام المرجع داخل المجموعة التي يشير إليها، فمثلا في المثال السابق يمكننا كتابة النمط هكذا (\1two|(one))+ وهذا أيضاًسيقوم بمطابقة oneonetwo. كيف ذلك؟
- لدينا مجموعتان، الأولى محاطة بالأخضر والثانية محاطة بالبنفسج
- عندما يبدأ المحرك بالمطابقة، يحاول بمطابقة المرجع \1 وهو لم يقم بمطابقة شيء بعد
- وبالتالي الخيار الأول بالكامل قبل علامة التبديل | لم يحدث مطابقة له
- ينتقل المحرك إلى قبل الحرف o في النمط ويبدأ البحث في النص فتحدث مطابقة مع o في بداية النص
- يستمر المحرك إلى أن تحدث مطابقة للخيار الثاني كاملاً مع one في النص.
- بما أن الخيار الثاني يوجد داخل مجموعة يتم تخزين المطابقة في المرجع الخاص بالمجموعة وهو \2
- وبما أن المموعة الثانية تقع داخل المجموعة الأولى، بالتالي أصبحت نتيجة المطابقة للمجموعة الأولى \1 أيضاً هي one.
- ينتقل المحرك لعامة + فيعرف أنه لم ينتهي من المطابقة بعد ويحاول البحث أكثر عن متطابقات فيقوم بتكرار المجموعة الأولى
- يقوم المحرك بتكرار البحث عن المجموعة الأولى بالرجوع قبل \1 في نفس الوقت الذي يقف فيه بعد one الأولى
- عندما يقوم المحرك بالرجوع قبل \1 يجد أن هذا المرجع يحتوي على one فيصبح الخيار الأول في البحث هو onetwo
- وهذا بالضبط ما هو موجود بعد one الأولى وتحدث مطابقة كاملة مع oneonetwo.
أغلب المحركات تفهم المرجع المتداخل ولكن محرك PCRE به مشكلة في التعامل معها ولكن هناك تحايل على ذلك بأن تجعل المجموعة التي تحتوي على مرجع أمامي بأن تكون مجموعة ذرية Regex atomic group (نتجحدث عنها في موضوع منفصل) فتكتب هكذا (?>(\1two|(one)))+ وبالطبع هذا ينطبق على اللغات التي تعتمد على PCRE مثل PHP,Delphi.