מדריך PHP: ביטויים רגולריים
באחד המאמרים הקודמים למדנו על העלאות קבצים. בסופו של המאמר הזהרתי שאין להעלות קבצים סתם כך בלי בדיקה. גם פלט מטפסים (שעליהם למדנו במאמרים הקודמים) צריך לבדוק. הדרך הטובה ביותר לבדיקה היא באמצעות ביטויים רגולריים או בקיצור Regex.
הביטויים הרגולריים מאפשרים לנו לעשות מניפולציות על כל מחרוזת טקסט שהיא – בין אם לבדוק אותה (האם למשל מחרוזת הטקסט היא כתובת דואר, או האם מחרוזת הטקסט היא מספר) ובין אם לשנות אותה (לעשות copy&paste) לחלק מהטקסט. יש שני סוגים של ביטויים רגולריים ש-PHP תומכת בהם. הסוג הראשון הוא POSIX והוא מבוסס על התקנים של UNIX. הסוג השני הוא PCRE שמבוסס על ביטויים רגולריים של שפת פרל. רוב המתכנתים משתמשים ב-PCRE ואנו נתרכז בה.
הנה החלקים הרלוונטיים וההסברים בדוקומנטציה על text function.
כאשר אנו באים להשתמש בביטויים רגולריים, אנחנו צריכים ראשית כל להחליט בשביל מה בדיוק אנו זקוקים לביטויים האלו. האם אנו רוצים לחתוך חלק מהטקסט? האם אנו רוצים לבצע search&replace? האם אנו רוצים לבצע בדיקה בטקסט? ישנן מגוון של פונקציות PCRE, אנו כרגע נתמקד באחת שמטרתה למצוא טקסט. שמה בישראל הוא preg_match ותפקידה למצוא תבנית מסוים במחרוזת טקסט. אם נסתכל עליה בדוקומנטציה, אנו נראה שהיא מקבל שני ארגומנטים – הראשון הוא ה-pattern – הלא הוא הביטוי הרגולרי והשני הוא הטקסט שבו היא מבצעת את הבדיקה. במידה והבדיקה יוצאת מוצלחת (הביטוי הרגולרי נמצא) היא תחזיר true (1). במידה והבדיקה יוצאת לא מוצלחת (הביטוי הרגולרי לא נמצא) היא תחזיר false (0).
נשמע מסובך? למה שלא נדגים? יש לי את המחרוזת המופלאה 'abc' בואו ונבדוק אם יש בה את האות a.
<?php $my_text = 'abc'; $result = preg_match('/a/', $my_text); print "Result: $result";
אם נריץ את זה נראה שהתוצאה היא 1, כלומר מצאנו את a. הסלאשים הם חיוניים ב-PECL והם נקראים דילמיטרים, אפשר להשתמש גם ב-# או בסוגריים מסולסלות:
<?php $my_text = 'abc'; $result = preg_match('{a}', $my_text); print "Result: $result";
אבל יש לזה בדיוק אותה משמעות כמו השימוש בדילימטר של סלאש, אז אנחנו נמשיך להשתמש בסלאש כי זה מה שרוב המתכנתים עושים.
בואו נתקדם לאט ובזהירות, נניח שאני רוצה לחפש בטקסט שלי את המילה: 'bc' ולא את המילה a, איך אני עושה את זה? פשוט ביותר! ממש כך:
<?php $my_text = 'abc'; $result = preg_match('/bc/', $my_text); print "Result: $result";
טוב, זה ממש פשוט. יותר מדי פשוט, בואו נסבך את העניינים קצת. נניח שאני רוצה למצוא את צירוף המילים ab, אבל רק כאשר הוא בתחילת משפט! כלומר, abc זה בסדר אבל zabc זה לא (כי ab לא בתחילת המשפט). איך עושים את זה? בדיוק בשביל זה יש לנו ביטויים רגלוטוריים. הביטוי הראשון הוא ^ – שאם הוא נמצא בתחילת הביטוי אז מדובר בתחילת שורה:
<?php $my_text = 'abc'; $result = preg_match('/^ab/', $my_text); print "Result: $result";
זה יחזיר לי תוצאה, כיוון שהביטוי מתיחס לכל טקסט שמתחיל ב-ab. לפיכך זה יתפוס על טקסט abc, abcd וגם ababulele לצורך העניין. הביטוי כולל כל מחרוזת טקסט שמתחילה ב-ab.
כמו שיש לנו ביטוי שמציין התחלה, יש לנו ביטוי שמציין סוף שורה. הביטוי הזה למשל:
bc$ יציין כל טקסט שהוא שנגמר ב-bc. למשל abc או bababababc
<?php $my_text = 'ababababababc'; $result = preg_match('/bc$/', $my_text); print "Result: $result";
אם אנו רוצים לשלב בין שני הביטויים? למשל ביטוי שמתחיל ב-a או נגמר ב-bc? מאד פשוט! משתמשים ב-|
<?php
$my_text = 'adfdfdfdbc'; $result = preg_match('/^a|bc$/', $my_text); print "Result: $result";
הביטוי הרגולרי ^a|bc$ (או מתחיל ב-a או נגמר ב-bc) יתפוס לגבי abc, abcfsfbc או כל ביטוי אחר.
ואם אנו רוצים ביטוי שגם מתחיל ב-a וגם נגמר ב-bc? כלומר – ביטוי שיתפוס גם את abc, גם את agfgfgfgbc ובעצם כל דבר שמתחיל ב-a ונגמר ב-bc? כלומר, בין a לבין bc יהיו אפס או יותר תוים (כמה בדיוק? לא אכפת לנו). כך עושים את זה:
<?php $my_text = 'adad bc'; $result = preg_match('/^a.*bc$/', $my_text); print "Result: $result";
לא להכנס לפאניקה! מה יש לנו פה? יש לנו סימן של תחילת שורה ^ ואז a שמראה לנו שחייב שיהיה a בהתחלה. בסוף יש לנו סימן של סוף ביטוי שמראה לנו ש-bc צריכים להיות בסוף. מה שחדש ומוזר הוא .* שני סימנים חדשים. נקודה זה כל תו שהוא – מספר, סימן מיוחד, אות בעברית, אנגלית ובעצם הכל. כוכבית היא סימן מיוחד שאומרת אפס או יותר. כלומר אני מגדיר בביטוי הזה שחייב להיות a בתחילתו, חייב להיות bc בסופו ובאמצע יכול להיות כל תו בכמות של אפס או יותר.
ואם הייתי רוצה 1 או יותר? אז זה פשוט, במקום כוכבית אפשר להשתמש בסימן +
<?php $my_text = 'addbc'; $result = preg_match('/^a.+bc$/', $my_text); print "Result: $result";
בביטוי כזה addbc יתפוס, גם ביטוי כמו assnfjskfjdskbc יתפוס. מה לא יתפוס? נכון abc כי בין a ל-bc צריך להיות אחד או יותר תווים.
ואם הייתי רוצה שבין a ל-b יהיה או אפס או תו אחד בלבד? גם זה פשוט, במקום + נשתמש בסימן שאלה
<?php $my_text = 'adbc'; $result = preg_match('/^a.?bc$/', $my_text); print "Result: $result";
בדוגמא הזו abc יתפוס, adbc יתפוס וכל ביטוי אחר שיש תו אחד בלבד בין a ל-bc. ביטוי כמו addbc שיש בו שני תוים בין a ל-bc לא יתפוס.
אם אני רוצה כמות ספציפית של תוים, אני יכול לכתוב אותן עם סוגריים מסולסלות שמפרטות את המקסימות והמינימום:
<?php $my_text = 'aghgddbc'; $result = preg_match('/^a.{2,5}bc$/', $my_text); print "Result: $result";
בסוגריים המסולסלות יש פירוט של שני תוים מינימום וחמישה תוים מקסימום בין ה-a ל-bc
עד עכשיו השתמשנו בנקודה, שפירושה הוא כל תו, אם אני אחליף את הנקודה בסימן (נניח @) אז הביטוי יהיה נכון רק לתוים מסוג @. הכי טוב להבין עם דוגמא:
<?php $my_text = 'a@@bc'; $result = preg_match('/^a@{2,5}bc$/', $my_text); print "Result: $result";
שימו לב לביטוי, במקום . שמתי @ ועכשיו כל מחרוזת טקסט צריכה להיות עם @ במקום עם כל תו שהוא.
אני לא חייב להיות מוגבל לתו אחד בלבד, בואו ונראה איך אפשר להשתמש גם ב-@ וגם ב-a לצורך העניין:
<?php
$my_text = 'aaa@@bc'; $result = preg_match('/^a[@a]{2,5}bc$/', $my_text); print "Result: $result";
איך עשיתי את זה? באמצעות סוגריים מרובעים, שבהם אני יכול לשים את כל התוים שאותם אני רוצה.
נניח שבין ה-a ל-bc אני רוצה שיהיו רק אותיות באנגלית מ-a ועד z? טוב, זה קל באמצעות סוגריים מרובעות, שם אני אוכל לשים את כל התוים שאני רוצה, אבל נראה לכם שאכתוב את כל האותיות מ-a ועד z? בשביל זה יש טווחים. מספיק לי לכתוב a-z:
<?php $my_text = 'aggbc'; $result = preg_match('/^a[a-z]{2,5}bc$/', $my_text); print "Result: $result";
בדוגמא הזו הביטוי affbc יתפוס, אבל ביטוי כמו aff1bc לא יתפוס כי 1 הוא לא בטווח המספרים:
אני יכול גם להוסיף טווחים נוספים, כמו 0-9 ו-A-Z לאותיות גדולות:
<?php $my_text = 'agg11Abc'; $result = preg_match('/^a[a-zA-Z0-9]{2,5}bc$/', $my_text); print "Result: $result";
הסימן ^ משמש גם לשלילה אם הוא מופיע בתוך הסוגריים המרובעות, כך למשל
<?php
$my_text = 'aggAbc'; $result = preg_match('/^a[^b]*bc$/', $my_text); print "Result: $result";
בגלל ש-^b נמצא בתוך הסוגריים המרובעות, המשמעות שלו היא כל תו חוץ מ-b.
סוגריים עגולים מאפשרים לנו ליצור תת תבנית, זה שימושי במקרים מסוימים – כמו למשל בבדיקת מייל:
<?php $my_text = '[email protected]'; $result = preg_match('/^[a-zA-Z0-9]+@[a-zA-Z0-9]+.[a-z]+(.[a-z]+)?$/', $my_text); print "Result: $result";
ביטויים רגולריים הם לפעמים סיוט לא קטן ונורא מבלבלים ללמידה, אבל חייבים ללמוד אותם. הדבר שאותו אני מציע לכם הוא פשוט להתאמן, אימון, אימון ושוב פעם אימון יאפשרו לכם לשלוט בכלי החשוב הזה. אין ברירה אלא להתאמן.
מודיפקטורים
אנו יכולים להוסיף לביטויים רגולריים ב-PECL גם מודיפקטורים באופן הבא:
<?php $my_text = 'ABC'; $result = preg_match('/^abc$/i', $my_text); print "Result: $result";
המודפיקטור במקרה הזה הוא i – והוא גורם לביטוי הרגולרי להתעלם מנושא האותיות הראשיות, כלומר במקרה הזה המחרוזת יכולה להיות abc או ABC ועדיין הביטוי הרגולרי יתפוס.
מודיפקטור m גורם לביטוי הרגולרי להתיחס ל-$ ול-^ לפי כל שורה. באופן רגיל, הביטוי הרגולרי ייבדק על כל הטקסט כאילו הוא שורה ארוכה אחת גם אם יש לנו שורות (ירידת שורה ב-PHP היא n אם מדובר במרכאות כפולות). אם נשתמש במודיפקטור m, אז ה-$ וה-^ יחושבו לפי כל שורה. דוגמא מסבירה את הכל:
<?php $my_text = "anbcnabc"; print $my_text; $result = preg_match('/^abc$/m', $my_text); print "Result: $result";
מודיפקטור x גורם להתעלמות ברווחים הלבנים בביטוי הרגולרי, למשל:
<?php $my_text = 'abc'; $result = preg_match('/^ab c$/x', $my_text); print "Result: $result";
הביטוי הזה יתפוס, אנו נראה 1.
מודיפקטור s יגרום ל'כל תו' שמסומן כנקודה להתיחס גם לירידת שורה. כך למשל:
PrFont34Bin0BinSub0Frac0Def1Margin0Margin0Jc1Indent1440Lim0Lim1<?php $my_text = "anbc"; $result = preg_match('/^a.bc$/s', $my_text); print "Result: $result";
אם נוריד את המודיפקטור תראו שנראה 0 – כלומר הביטוי לא תופס.
יש עוד כמה מודיפקטורים שניתן לקרוא אודותיהם בדוקומנטציה של PCRE.
תגובות בפייסבוק