عبارت‌های باقاعده یا regular expressions

Regular Expressions یا عبارت‌های باقاعده که با نام RegEx نیز شناخته می‌شوند، عبارت‌هایی هستند که طبق الگوی (Pattern) خاصی، تطبیق یک استرینگ (string) در استرینگ (رشته) دیگری را بررسی می‌کنند. در واقع ریجکس نوع خاصی از پترن است که در بسیاری از زبان‌های برنامه‌نویسی و اپلیکیشن‌های مدرن قابل استفاده است. با استفاده از آن می‌توان داده‌های ورودی کاربر را اعتبارسنجی کرد، رشته‌ای را داخل رشته دیگر پیدا کرد و رشته پیدا شده را با رشته دیگری جایگزین کرد. کاربردهای RegEx شامل جستجو، جایگزینی و اعتبارسنجی است. برای مثال در اعتبارسنجی شماره تلفن، ایمیل و ... استفاده می‌شوند. در واقع ریجکس چیزی بیشتر از پترن یا دنباله‌ای از کاراکترها نیست. با کمک ریجکس می‌توانید یک استرینگ را به چندین تکه (chunk) قسمت کنید. عبارت‌های باقاعده

عبارت‌های باقاعده

ساده‌ترین ریجکس یافتن یک کاراکتر در یک استرینگ (مثلا w در Hello World) است.

براکت

براکت‌ها ([]) هنگامی که در یک استرینگ استفاده شوند، معنی و مفهوم خاصی دارند. برای یافتن محدوده‌ای از کاراکترها استفاده می‌شوند.

عبارت توضیح
[0-9] کاراکترهای عددی بر این عبارت منطق (Match) هستند.
[a-z] حروف کوچک انگلیسی منطبق بر این عبارت هستند.
[A-Z] حروف بزرگ انگلیسی بر این عبارت منطبق هستند.
[a-zA-Z] همه حروف بزرگ و کوچک انگلیسی بر این عبارت منطبق هستند.
[abc] حروف a و b و c بر این عبارت‌ها منطبق هستند.
[^abc] هر کاراکتری غیر از a و b و c بر این محدوده منطبق هستند.
[0-3] هر کاراکتری عددی از 0 تا 3 بر این محدوده منطبق هستند.

محدوده‌های نشان داده شده در جدول بالا قابل شخصی‌سازی و تغییر هستند، مثلا می‌توانید محدوده را از حرف b تا f و یا هر محدوده دیگری انتخاب کنید.

پرانتز

براکت مشخص کننده تنها یک کاراکتر است، مگر اینکه بعد از آن بلافاصله Quantifier استفاده کنیم. ولی عبارت داخل پرانتز ()، مجموعه‌ای از کاراکترها با ترتیب مشخص است. برای مثال [abc] یعنی

یکی از این سه کاراکتر باید یافت شود، ولی (abc) یعنی دقیقا باید abc با همین ترتیب و وجود هر سه حرف، یافت شود.

Quantifiers

با استفاده از برخی کاراکترهای خاص می‌توانید تعداد دفعات تکرار و موقعیت دنباله کاراکترهای داخل براکت، کاراکترهای تک را مشخص کنید. فلگ‌هایی (flag)  از جمله +، *، ?، $ و {3} و {3,4}، بعد از کاراکترها برای همین کار استفاده می‌شوند.

عبارت توضیح
+ مجموعه‌ای از کاراکترها که با کاراکتر یا براکت قبل از + حداقل یک‌بار منطبق باشند. مثلا [a-z]+ عبارت‌های الزام به وجود حداقل یک حرف الفبا دارد.
* این کاراکتر بر هر استرینگی شامل 0 دفعه تکرار یا بیشتر، منطبق است.
? این کاراکتر بر هر استرینگی شامل 0 یا 1 مرتبه تکرار، منطبق است.
{N} تعداد دقیق دفعات تکرار را با عددی به جای N مشخص می‌کنیم.
{2,3} حداقل و حداکثر تکرار را مشخص می‌کند.
{2, } حداقل تکرار را مشخص می‌کند و حداکثری وجود ندارد.
$ این کاراکتر بعد از هر عبارتی استفاده شود، برای انطباق، باید عبارت قبل از آن در انتهای استرینگ باشد.
^ این کاراکتر بعد از هر عبارتی استفاده شود، برای انطباق، باید عبارت قبل از آن در ابتدای استرینگ باشد.

مثال

مثال‌های زیر به درک بهتر مفهوم RegEx کمک می‌کنند.

عبارت توضیح
[^a-zA-Z] هر کاراکتری غیر از حروف بزرگ و کوچک انگلیسی بر این عبارت منطبق است.
p.p هر استرینگی شامل دو p و یک کاراکتر دیگر بین آن بر این عبارت منطبق است. مثلا در یک استرینگ که کلمه‌paper وجود داشته باشد، pap با این عبارت منطبق است.
^.{2}$ این عبارت بر استرینگ به طول 2 کاراکتر منطبق است.
<b>(.*)</b> این عبارت با مجموعه تگ b و استرینگ داخل آن منطبق است. (اگر آیدنتیفایر شما / بود، از <b>(.*)<\/b> استفاده کنید.)
(png|jpeg|jpg) این عبارت با png و jpg و jpeg مطابقت دارد.

متا کاراکترها

یک متا کاراکتر، یک کاراکتر الفبایی ساده است که پس از یک یک‌اسلش (\) می‌آید، این ترکیب معنای و مفهوم خاصی دارد. برای مثال، در رشته‌های عددی می‌توان رقم‌های بزرگ را پیدا کرد.

عبارت توضیح
. هر کاراکتری غیر از line break
\s هر کاراکتر وایت‌اسپیس (space و tab و newline)
\S هر کاراکتری غیر از کاراکترهای وایت‌اسپیس
\d هر کاراکتر عددی (0-9)
\D هر کاراکتری غیر از اعداد
\w هر کاراکتری شامل حروف کوچک و بزرگ، اعداد و آندرسکور (_)
\W  هر کاراکتری غیر از حروف کوچک و بزرگ، اعداد و آندرسکور (_)
[aeiou]  با حروف صدادار مطابقت دارد.
[^aeiou]  هر کاراکتری غیر از کاراکترهای داخل براکت
(foo|bar|baz)  یکی از کلمه داخل پرانتز

Pattern Modifiers

الگوهای اصلاح‌کننده یا پترن‌مدیفایرهای مختلفی وجود دارند که کار با ریجکس را ساده‌تر می‌کنند:

عبارت توضیح
i حساسیت به حروف بزرگ و کوچک را در هنگام بررسی عبارت‌ها از بین می‌برد.
m  وقتی استرینگ شامل چند خط یا به اصطلاح Multiline (کاراکترهای \r و \n) باشد، از این مدیفایر استفاده می‌شود.

برای کسب اطلاعات بیشتر درباره سایر مدیفایرها به سایت Regex Tutorial مراجعه کنید.

فانکشن‌های PHP برای کار با عبارت‌های باقاعده

این فانکشن‌ها با کلمه preg_ شروع می‌شوند:

عبارت توضیح
preg_filter() جستجو و جایگذاری مقادیر را انجام می‌دهد.
preg_grep() آرایه ای از موارد منطبق با پترن برمی‌گرداند.
preg_last_error کد خطای (error) آخرین فانکشن ریجکس اجرا شده را برمی‌گرداند.
preg_match() اگر مطابقی در استرینگ پیدا کند، 1 در غیرانصورت 0 برمی‌گرداند.
preg_match_all() تعداد تطابق‌ها را برمی‌گرداند که 0 هم می‌تواند باشد.
preg_quote() اول کاراکترهای رزرو شده عبارت‌های باقاعده، که در رشته بیابد، بک اسش (\) اضافه می‌کند. به اصلاح Escape کاراکترهای رزرو شده را انجام می‌دهد.
preg_replace() جستو و جایگذاری مقادیر منطبق را انجام می‌دهد.
preg_replace_callback()  جستجو را انجام می‌دهد، در صورت تطبیق یک کال‌بک (Callback) اجرا می‌شود.
preg_replace_callback_array()  جستجو را طبق پترن‌های مشخص شده، انجام می‌دهد که برای پترن کال‌بک مربوط به آن را اجرا می‌کند.
preg_split()  یک استرینگ را طبق پترن مشخصی به چند قسمت تبدیل می‌کند و به صورت آرایه برمی‌گرداند.

کال‌بک یک کد قابل اجرا است که به عنوان یک آرگومنت به کد دیگری پاس داده می‌شود تا اجرا شود.

چند مثال‌ برای کار با فانکشن‌ها

مثال‌های بالا از سایت php آورده شده است:

<?php
$subject = array('1', 'a', '2', 'b', '3', 'A', 'B', '4'); 
$pattern = array('/\d/', '/[a-z]/', '/[1a]/'); 
$replace = array('A:$0', 'B:$0', 'C:$0'); 

echo "preg_filter returns\n";
print_r(preg_filter($pattern, $replace, $subject)); 

echo "preg_replace returns\n";
print_r(preg_replace($pattern, $replace, $subject));

خروجی مثال بالا:

preg_filter returns
Array
(
    [0] => A:C:1
    [1] => B:C:a
    [2] => A:2
    [3] => B:b
    [4] => A:3
    [7] => A:4
)
preg_replace returns
Array
(
    [0] => A:C:1
    [1] => B:C:a
    [2] => A:2
    [3] => B:b
    [4] => A:3
    [5] => A
    [6] => B
    [7] => A:4
)

مثال دوم:

<?php

preg_match('/(?:\D+|<\d+>)*[!?]/', 'foobar foobar foobar');

if (preg_last_error() == PREG_BACKTRACK_LIMIT_ERROR) {
    print 'Backtrack limit was exhausted!';
}

خروجی مثال دوم:

Backtrack limit was exhausted!

مثال سوم:

<?php
preg_match('/(foo)(bar)(baz)/', 'foobarbaz', $matches, PREG_OFFSET_CAPTURE);
print_r($matches);

خروجی مثال سوم:

Array
(
    [0] => Array
        (
            [0] => foobarbaz
            [1] => 0
        )

    [1] => Array
        (
            [0] => foo
            [1] => 0
        )

    [2] => Array
        (
            [0] => bar
            [1] => 3
        )

    [3] => Array
        (
            [0] => baz
            [1] => 6
        )

)

مثال چهارم:

<?php
// get host name from URL
preg_match('@^(?:http://)?([^/]+)@i',
    "http://www.php.net/index.html", $matches);
$host = $matches[1];

// get last two segments of host name
preg_match('/[^.]+\.[^.]+$/', $host, $matches);
echo "domain name is: {$matches[0]}\n";

خروجی مثال چهام:

domain name is: php.net

مثال پنجم:

<?php
// The \\2 is an example of backreferencing. This tells pcre that
// it must match the second set of parentheses in the regular expression
// itself, which would be the ([\w]+) in this case. The extra backslash is
// required because the string is in double quotes.
$html = "<b>bold text</b><a href=howdy.html>click me</a>";

preg_match_all("/(<([\w]+)[^>]*>)(.*?)(<\/\\2>)/", $html, $matches, PREG_SET_ORDER);

foreach ($matches as $val) {
    echo "matched: " . $val[0] . "\n";
    echo "part 1: " . $val[1] . "\n";
    echo "part 2: " . $val[2] . "\n";
    echo "part 3: " . $val[3] . "\n";
    echo "part 4: " . $val[4] . "\n\n";
}

خروجی مثال پنجم:

matched: <b>bold text</b>
part 1: <b>
part 2: b
part 3: bold text
part 4: </b>

matched: <a href=howdy.html>click me</a>
part 1: <a href=howdy.html>
part 2: a
part 3: click me
part 4: </a>

مثال ششم:

<?php
$keywords = '$40 for a g3/400';
$keywords = preg_quote($keywords, '/');
echo $keywords; // returns \$40 for a g3\/400

مثال هفتم:

<?php
// In this example, preg_quote($word) is used to keep the
// asterisks from having special meaning to the regular
// expression.

$textbody = "This book is *very* difficult to find.";
$word = "*very*";
$textbody = preg_replace ("/" . preg_quote($word, '/') . "/",
                          "<i>" . $word . "</i>",
                          $textbody);

مثال هشتم:

<?php
// split the phrase by any number of commas or space characters,
// which include " ", \r, \t, \n and \f
$keywords = preg_split("/[\s,]+/", "hypertext language, programming");
print_r($keywords);

خروجی مثال هشتم:

Array
(
    [0] => hypertext
    [1] => language
    [2] => programming
)

مثال نهم:

<?php
$string = 'The quick brown fox jumps over the lazy dog.';
$patterns = array();
$patterns[0] = '/quick/';
$patterns[1] = '/brown/';
$patterns[2] = '/fox/';
$replacements = array();
$replacements[2] = 'bear';
$replacements[1] = 'black';
$replacements[0] = 'slow';
echo preg_replace($patterns, $replacements, $string);

خروجی مثال نهم:

The bear black slow jumps over the lazy dog.

مثال دهم:

<?php
// this text was used in 2002
// we want to get this up to date for 2003
$text = "April fools day is 04/01/2002\n";
$text.= "Last christmas was 12/24/2001\n";
// the callback function
function next_year($matches)
{
  // as usual: $matches[0] is the complete match
  // $matches[1] the match for the first subpattern
  // enclosed in '(...)' and so on
  return $matches[1].($matches[2]+1);
}
echo preg_replace_callback(
            "|(\d{2}/\d{2}/)(\d{4})|",
            "next_year",
            $text);

خروجی مثال دهم:

April fools day is 04/01/2003
Last christmas was 12/24/2002

مثال یازدهم:

<?php
$subject = 'Aaaaaa Bbb';

preg_replace_callback_array(
    [
        '~[a]+~i' => function ($match) {
            echo strlen($match[0]), ' matches for "a" found', PHP_EOL;
        },
        '~[b]+~i' => function ($match) {
            echo strlen($match[0]), ' matches for "b" found', PHP_EOL;
        }
    ],
    $subject
);

خروجی مثال یازدهم:

6 matches for "a" found
3 matches for "b" found

جمع‌بندی و نکات پایانی

هشدار: کاراکتر . ریسک سواستفاده دارد، پس در استفاده از آن دقت کنید. مثلا \d\d.\d\d.\d\d علاوه بر اینکه برای اعتبارسنجی تاریخ شکل 96/11/24 را معتبر می‌شناسد، عبارت 132459 را نیز معتبر می‌داند. راه حل مناسب \d\d[/.\-]\d\d[/.\-]\d\d است. تنها زمانی از این کاراکتر استفاده کنید که واقعا می‌خواهید همه کاراکترها را مجاز بدانید.
هشدار: فانکشن codeg_match() ممکن است FALSE یا 0 برگرداند، برای اطمینان حتما مقدار بازگشتی را با استفاده از عملگر مقایسه‌ای === مورد بررسی قرار دهید.
توجه کنید: اگر فقط قصد دارید وجود یا عدم وجود یک استرینگ را در استرینگ دیگری چک کنید، از فانکشن strpos() استفاده کنید که نسبت به codeg_match() سریعتر است.
توجه کنید: اگر به قدرت عبارات با قاعده نیاز ندارید، از فانکشن‌های explode() یا str_split() استفاده کنید.
توجه کنید: اگر فانکشن codeg_split() با هیچ استرینگی مچ (Match) نشود، یک آرایه تک عنصری برمیگرداند که مقدار آن رشته ورودی خواهد بود.