التعبيرات النمطية المعقدة (Complex Regular Expressions)
أثناء بحثي مؤخرًا عن تحليل HTML، صادفت تعبيرًا منتظمًا:
/([\w-:\*>]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-:]+)(?:([!*^$]?=)["']?(.*?)["']?)?\])?([\/, ]+)/is
هذا التعبير النمطي (regex) يستخدم لمطابقة أنماط معينة في النصوص. دعونا نشرح كل جزء منه:
-
([\w-:\*>]*)
: يطابق أي حرف من الحروف (a-z, A-Z)، الأرقام (0-9)، الشرطة السفلية (_
)، الشرطة (-
)، النقطتين (:
)، النجمة (*
)، أو علامة أكبر من (>
). يمكن أن يتكرر هذا النمط صفر أو أكثر من المرات. (?:\#([\w-]+)|\.([\w-]+))?
: هذا جزء اختياري (بسبب?
في النهاية) يطابق إما:\#([\w-]+)
: علامة#
متبوعة بواحد أو أكثر من الحروف أو الأرقام أو الشرطة السفلية أو الشرطة.- أو
\.([\w-]+)
: نقطة.
متبوعة بواحد أو أكثر من الحروف أو الأرقام أو الشرطة السفلية أو الشرطة.
(?:\[@?(!?[\w-:]+)(?:([!*^$]?=)["']?(.*?)["']?)?\])?
: هذا جزء اختياري آخر يطابق:\[
: قوس مربع افتتاح[
.@?
: علامة@
اختيارية.(!?[\w-:]+)
: علامة تعجب!
اختيارية متبوعة بواحد أو أكثر من الحروف أو الأرقام أو الشرطة السفلية أو الشرطة أو النقطتين.(?:([!*^$]?=)["']?(.*?)["']?)?
: جزء اختياري يطابق:([!*^$]?=)
: علامة تعجب!
، نجمة*
، علامة^
، أو علامة$
اختيارية متبوعة بعلامة يساوي=
.["']?(.*?)["']?
: نص محاط بعلامتي تنصيص"
أو'
اختيارية.
\]
: قوس مربع إغلاق]
.
-
([\/, ]+)
: يطابق واحد أو أكثر من الشرطة المائلة/
، الفاصلة,
، أو المسافة ` `. is
: هذه علامات تعديل:i
: يجعل المطابقة غير حساسة لحالة الأحرف (أي لا يفرق بين الأحرف الكبيرة والصغيرة).s
: يجعل النقطة.
تطابق أي حرف، بما في ذلك حرف السطر الجديد.
هذا التعبير النمطي يمكن استخدامه لتحليل وفهم أنماط معينة في النصوص، مثل العناوين أو العلامات في لغة Markdown أو HTML.
يُستخدم لمطابقة محددات CSS، مثل div > ul
.
لقد صادفت في الماضي العديد من التعبيرات المعقدة كهذه، وكنت أتراجع عنها بشكل غريزي. اليوم، قررت أن أفهمها تمامًا! يا رجل، يجب أن تكون قاسيًا على نفسك!
مطابقة div > ul
في CSS، يمكنك استخدام المحدد div > ul
لتحديد عناصر <ul>
التي تكون أبناء مباشرين لعناصر <div>
. هذا يعني أن العنصر <ul>
يجب أن يكون مباشرة داخل عنصر <div>
، ولا يمكن أن يكون داخل أي عناصر أخرى متداخلة.
مثال:
<div>
<ul>
<li>عنصر 1</li>
<li>عنصر 2</li>
</ul>
</div>
في هذا المثال، سيتم تطبيق الأنماط المحددة باستخدام div > ul
على العنصر <ul>
الموجود داخل <div>
.
مثال آخر:
<div>
<div>
<ul>
<li>عنصر 1</li>
<li>عنصر 2</li>
</ul>
</div>
</div>
في هذا المثال، لن يتم تطبيق الأنماط على العنصر <ul>
لأنه ليس ابنًا مباشرًا للعنصر <div>
الخارجي، بل هو ابن لعنصر <div>
آخر متداخل.
استخدام في CSS:
div > ul {
background-color: yellow;
}
سيؤدي هذا الكود إلى تغيير لون خلفية العنصر <ul>
الذي يكون ابنًا مباشرًا لعنصر <div>
إلى اللون الأصفر.
لقد وجدت موقعًا https://regex101.com/ ، يمكنه مطابقة التعبيرات النمطية (regex) عبر الإنترنت ويقدم أيضًا تفسيرات.
بالرغم من وجود الشرح على اليمين، أصبحت الأمور أكثر وضوحًا. لكن ما زلت غير متأكد من كيفية تطابقها بالضبط. لذا، سأبحث عن بعض الأمثلة وأحللها واحدة تلو الأخرى.
الكود الذي يحتوي على هذا التعبير النمطي (Regular Expression) هو:
$matches = [];
preg_match_all($this->pattern, trim($selector).' ', $matches, PREG_SET_ORDER);
preg_match_all
تعني الحصول على جميع السلاسل التي تطابق النمط. إذا كان لديك:
preg_match_all("abc", "abcdabc", $matches)
في الكود أعلاه، يتم استخدام الدالة preg_match_all
في لغة PHP للبحث عن جميع التطابقات لنمط معين داخل نص معين. في هذه الحالة، النمط المطلوب البحث عنه هو "abc"
، والنص الذي يتم البحث فيه هو "abcdabc"
. النتائج سيتم تخزينها في المصفوفة $matches
.
المعامل الأول هو النمط، والمعامل الثاني هو السلسلة النصية التي سيتم مطابقتها، والمعامل الثالث هو المرجع للنتيجة. بعد التشغيل، سيحتوي مصفوفة $matches
على عنصرين من abc
.
بعد فهم ذلك، يتطابق div > ul
في الصورة أعلاه فقط مع الأحرف الأربعة الأولى div >
. هل regex101
لا يدعم preg_match_all
؟ لحسن الحظ، يمكن إضافة مُعدِّل يُسمى g
لحل المشكلة:
بعد إضافة g
، سيتم مطابقة جميع النتائج، بدلاً من إرجاع المطابقة الأولى فقط.
بعد إضافتها، قمنا بمطابقة div > ul
:
على الجانب الأيمن، في المطابقة الأولى، أي div
، استخدمنا قواعد المجموعة الأولى لمطابقة div
، ثم استخدمنا قواعد المجموعة السابعة لمطابقة المسافة ` `.
دعونا نتابع شرح المجموعة الأولى من القواعد:
في هذه السلسلة الطويلة من التعبيرات، يُطلق على المجموعة الأولى المحاطة بالأقواس اسم المجموعة الأولى من القواعد. هذه مجموعة التقاط (capture group). الأقواس نفسها لا تُطابق أي شيء، بل تُستخدم للتجميع. []
تعني مجموعة أحرف، والقواعد بداخلها توضح نوع مجموعة الأحرف هذه. تحتوي مجموعة الأحرف هذه على:
\w
تعني الأحرف الكبيرة والصغيرة والأرقام من 0 إلى 9 بالإضافة إلى الشرطة السفلية.-:
تعني هذين الحرفين مباشرة في المجموعة.\*
لأن*
هو حرف محجوز في التعبيرات النمطية وله معنى خاص، لذا يجب استخدام\
للتهريب، مما يعني أن هذا حرف*
عادي.>
تعني ببساطة حرف>
.
[\w-:\*>]*
في النهاية، يشير *
إلى أن الأحرف السابقة يمكن أن تظهر 0 مرة أو عدة مرات، ولكن يجب أن تتطابق مع أكبر عدد ممكن من المرات. السبب في أنه يمكن أن يتطابق مع div
هو أن \w
يتطابق مع d
و i
و v
. السبب في أنه لا يستمر في التطابق مع المسافة التي تليها هو أن المسافة غير موجودة في []
. معنى مجموعة الالتقاط هو أن هذه المجموعة من التطابقات ستظهر في مصفوفة النتائج. في المقابل، هناك أيضًا مجموعات غير ملتقطة، والصيغة هي (?:)
. إذا لم تكن هناك حاجة إلى هذه المجموعة من النتائج في ([\w-:\*>]*)
، فيمكن كتابتها كـ (?:[\w-:\*>]*)
.
إذن، إذا لم تظهر في النتيجة، أليس من الأفضل عدم استخدام الأقواس على الإطلاق؟ الأقواس تُستخدم للتجميع، والتجميع له أهمية كبيرة. يمكنك الرجوع إلى What is a non capturing group? (?:) - StackOverflow لمزيد من المعلومات.
بعد أن انتهينا من شرح كيف أن div
يلبي المجموعة الأولى من القواعد، سنتحدث الآن عن سبب كون المسافة ` ` تلبي المجموعة السابعة من القواعد.
[\/, ]
تعني مطابقة أي من هذه الأحرف الأربعة، و +
تعني أن المطابقة السابقة تظهر مرة واحدة أو عدة مرات، ويجب أن تكون عدد المرات كبيرًا قدر الإمكان. ولأن هذه الأحرف الأربعة تشمل المسافة، فإنها تطابق المسافة لدينا. وبما أن الحرف التالي بعد div
هو >
، فإنه لم يعد يلبي قاعدة المجموعة السابعة، وبالتالي لا يستمر في المطابقة.
لقد فهمت كيفية مطابقة div
. ولكن لماذا لم تطابق القواعد من المجموعة الثانية إلى السادسة المسافات هنا، وتركتها للمجموعة السابعة؟
التفسير الخاص بالجزء الثاني:
أولاً، (?:)
تعني أن هذه مجموعة غير مُلتقطة. علامة الاستفهام ?
في النهاية تعني أن المطابقة السابقة يمكن أن تحدث صفر أو مرة واحدة. لذا، (?:\#([\w-]+)|\.([\w-]+))?
يمكن أن تكون موجودة أو غير موجودة. بعد إزالة المُعدِّلات الخارجية، يتبقى لدينا \#([\w-]+)|\.([\w-]+)
، حيث |
تعني “أو”، أي أن المطابقة يمكن أن تكون لأي من الجزأين. في \#([\w-]+)
، \#
تطابق حرف #
، و [\w-]+
تطابق الأحرف الأخرى. وفي النصف الثاني، \.([\w-]+)
، \.
تطابق حرف .
.
لذلك، قد لا تفي المجموعات من 2 إلى 6 بالمتطلبات لأن المسافة ليست الحرف الأول المطلوب لهذه المجموعات، وبما أن هذه المجموعات تحتوي على مُعدِّل ?
، فإن عدم الوفاء بالمتطلبات ليس مشكلة، وبالتالي يتم الانتقال إلى المجموعة السابعة.
بعد div > ul
، علامة >
تظل كما هي:
المجموعة الأولى من القواعد ([\w-:\*>]*)
تطابقت مع >
، والمجموعة السابعة من القواعد ([\/, ]+)
تطابقت مع مسافة. بعد ذلك، ul
تعمل مثل div
.
مطابقة #answer-4185009 > table > tbody > td.answercell > div > pre
هذا النص يصف مسارًا في DOM (نموذج كائن المستند) يستخدم لتحديد عنصر معين في صفحة ويب. لن أقوم بترجمة الأسماء أو الرموز البرمجية، ولكن يمكن تفسير المسار على النحو التالي:
#answer-4185009
: العنصر الذي يحتوي على المعرفanswer-4185009
.> table
: العنصر الفرعي المباشر الذي يكون من نوعtable
.> tbody
: العنصر الفرعي المباشر الذي يكون من نوعtbody
.> td.answercell
: العنصر الفرعي المباشر الذي يكون من نوعtd
وله الكلاسanswercell
.> div
: العنصر الفرعي المباشر الذي يكون من نوعdiv
.> pre
: العنصر الفرعي المباشر الذي يكون من نوعpre
.
هذا المسار يستخدم عادةً في CSS أو JavaScript لتحديد العنصر المطلوب بدقة.
ثم نأتي إلى محدد أكثر تعقيدًا قليلاً #answer-4185009 > table > tbody > td.answercell > div > pre
(يمكنك أيضًا فتح https://regex101.com/ ولصق هذا هناك للاختبار):
هذا تم نسخه ولصقه من Chrome:
المطابقة الأولى:
لأن القاعدة الأولى ([\w-:\*>]*)
تحتوي على مجموعة أحرف داخل []
لا يوجد فيها أي حرف يتطابق مع #
، وبما أن *
في النهاية تدعم التطابق 0 مرات أو أكثر، هنا التطابق هو 0 مرات. ثم وصف القاعدة الثانية هو:
لقد تم تحليل ذلك أعلاه. دعونا ننظر مباشرة إلى \#([\w-]+)
قبل |
، حيث \#
تطابق #
، و [\w-]+
تطابق answer-4185009
. بالنسبة إلى \.([\w-]+)
الذي يليه، إذا كان النص .answer-4185009
، فسيتم تطبيق هذا التطابق.
لننتقل الآن إلى المطابقة td.answercell
،
القاعدة الأولى ([\w-:\*>]*)
تطابقت مع td
، بينما الجزء الثاني من القاعدة الكبيرة (?:\#([\w-]+)|\.([\w-]+))?
، أي \.([\w-]+)
، تطابق مع .answercell
.
انتهى تحليل هذا المُحدِّد (Selector) هنا.
مطابقة a[href="http://google.com/"]
ثم ننتقل لمطابقة المحدد a[href="http://google.com/"]
:
لنلقِ نظرة على الكتلة الثالثة:
التعبير الخاص بالكتلة الثالثة هو (?:\[@?(!?[\w-:]+)(?:([!*^$]?=)["']?(.*?)["']?)?\])?
، حيث أن (?:)
في البداية يشير إلى أن هذه مجموعة غير قابلة للالتقاط، و ?
في النهاية يشير إلى أن هذه المجموعة الكبيرة يمكن أن تتطابق 0 أو 1 مرة. إذا قمنا بإزالة هذه الأجزاء، يصبح التعبير \[@?(!?[\w-:]+)(?:([!*^$]?=)["']?(.*?)["']?)?\]
.
\[
يتطابق مع الحرف[
.@?
يشير إلى أن الحرف@
يمكن أن يكون موجودًا أو غير موجود.- المجموعة التالية
(!?[\w-:]+)
، حيث!
يمكن أن تكون موجودة أو غير موجودة، و[\w-:]+
تتطابق معhref
. - المجموعة التالية
(?:([!*^$]?=)["']?(.*?)["']?)
هي مجموعة غير قابلة للالتقاط، وإذا قمنا بإزالة الطبقة الخارجية، يصبح التعبير([!*^$]?=)["']?(.*?)["']?
.- هنا
([!*^$]?=)
، حيث[!*^$]?
يشير إلى أنه يمكن أن يتطابق مع 0 أو 1 من الأحرف الموجودة داخل[]
. - ثم
=
يتطابق مباشرة. - ثم
["']?(.*?)["']?
، حيث يتطابق مع"http://google.com/"
، و["']?
يشير إلى أنه يمكن أن يتطابق مع"
أو'
أو لا يتطابق مع أي منهما. إذا قمنا بإزالة الطبقة الخارجية، يصبح التعبير(.*?)
الذي يتطابق معhttp://google.com/
، حيث*?
يشير إلى التطابق بأقل قدر ممكن، أي إذا كان هناك"
أو'
، سيتم تركها للتعبير["']?
للتطابق. لذلك لن يتطابق معhttp://google.com/"
، بل فقط معhttp://google.com/
.
- هنا
لذلك، فإن محدد العنصر الكامل a[href="http://google.com/
…
"]
يطابق النهاية.
الخلاصة
أخيرًا فهمت الأمر! دعونا نوضح الأمر مرة أخرى، أولاً يتكون التعبير المعقد بأكمله ([\w-:\*>]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-:]+)(?:([!*^$]?=)["']?(.*?)["']?)?\])?([\/, ]+)
من أربعة أجزاء رئيسية:
([\w-:\*>]*)
(?:\#([\w-]+)|\.([\w-]+))?
(?:\[@?(!?[\w-:]+)(?:([!*^$]?=)["']?(.*?)["']?)?\])?
([\/, ]+)
هذه التعبيرات النمطية (Regular Expressions) تستخدم لمطابقة أنماط معينة في النصوص. لن أقوم بترجمتها لأنها تحتوي على رموز وأكواد برمجية محددة يجب أن تبقى كما هي.
الجزء الثالث الأكثر تعقيدًا يتكون من هذه الأجزاء:
\[
(!?[\w-:]+)
(?:([!*^$]?=)["']?(.*?)["']?)?
\]
تمثل هذه العبارة النمطية (regex) نمطًا لتحليل العلامات أو السمات في النصوص، مثل تلك المستخدمة في لغة Markdown أو HTML. إليك شرح كل جزء:
\[
: يطابق حرف الافتتاح[
.(!?[\w-:]+)
: يطابق اسم السمة أو العلامة، حيث:!?
: يطابق حرف!
إذا كان موجودًا (اختياري).[\w-:]+
: يطابق واحدًا أو أكثر من الأحرف التي يمكن أن تكون حروفًا أو أرقامًا أو شرطات-
أو نقطتين:
.
(?:([!*^$]?=)["']?(.*?)["']?)?
: يطابق القيمة الاختيارية للسمة، حيث:(?: ... )
: مجموعة غير خازنة (non-capturing group).([!*^$]?=)
: يطابق عامل المقارنة (مثل=
,!=
,*=
,^=
,$=
) إذا كان موجودًا.["']?(.*?)["']?
: يطابق القيمة المحاطة بعلامات تنصيص"
أو'
إذا كانت موجودة.
\]
: يطابق حرف الإغلاق]
.
هذا النمط مفيد لاستخراج السمات أو العلامات من النصوص المهيكلة.
لذلك، يمكن التغلب على هذه الأجزاء الصغيرة بما يكفي واحدة تلو الأخرى. ثم ابحث عن المزيد من الأمثلة، وانظر كيف يتطابق كل مثال، واستخدم تفسير https://regex101.com/ لتحليلها. بهذه الطريقة، يمكنك فهم هذا التعبير العادي الذي يبدو معقدًا، وستكتشف أنه في الواقع “نمر من ورق”!