Regular Expressions در .NET؛ Regex Syntax و Cookbook

فصل ۲۵: Regular Expressions در .NET؛ Syntax، Groups و Cookbook

فصل ۲۵: Regular Expressions در .NET؛ Syntax، Groups و Cookbook

تصویر پرندهٔ آغاز فصل در منبع
نشان تصویری آغاز فصل در منبع

فصل ۲۵: Regular Expressions

زبان regular expression الگوهای کاراکتری را شناسایی می‌کند. Typeهای .NET برای regex بر پایهٔ regular expressionهای Perl 5 هستند و هم search و هم search/replace را پشتیبانی می‌کنند.

Regex برای کارهایی مانند validation ورودی متن ــ از جمله password و phone number ــ، parsing دادهٔ متنی به ساختارهای منظم‌تر، و جایگزینی patternهای متن در document استفاده می‌شود. این فصل هم بخش آموزشی برای مبانی regex در .NET دارد و هم بخش مرجع syntax زبان. همهٔ typeهای regular expression در System.Text.RegularExpressions تعریف شده‌اند.

مبانی Regular Expression

یکی از operatorهای رایج، quantifier است. علامت ? item قبلی را صفر یا یک بار match می‌کند، یعنی آن item optional است. Item می‌تواند یک character یا ساختار پیچیده‌تری در square bracket باشد. الگوی colou?r با color و colour match می‌شود ولی با colouur نه:

Console.WriteLine (Regex.Match ("color",   @"colou?r").Success);  // True
Console.WriteLine (Regex.Match ("colour",  @"colou?r").Success);  // True
Console.WriteLine (Regex.Match ("colouur", @"colou?r").Success);  // False

Regex.Match داخل string بزرگ‌تر search می‌کند. object برگشتی propertyهای Index، Length، Value و Success دارد:

Match m = Regex.Match ("any colour you like", @"colou?r");
Console.WriteLine (m.Success);     // True
Console.WriteLine (m.Index);       // 4
Console.WriteLine (m.Length);      // 6
Console.WriteLine (m.Value);       // colour
Console.WriteLine (m.ToString());  // colour

می‌توان Regex.Match را نسخهٔ قدرتمندتر string.IndexOf دانست؛ با این تفاوت که pattern را جست‌وجو می‌کند نه literal string. IsMatch میان‌بری برای Match و سپس بررسی Success است.

Regex engine به‌طور پیش‌فرض از چپ به راست کار می‌کند و بنابراین leftmost match برمی‌گردد. با NextMatch matchهای بعدی و با Matches همهٔ matchها قابل دریافت‌اند:

Match m1 = Regex.Match ("One color? There are two colours in my head!",
                        @"colou?rs?");
Match m2 = m1.NextMatch();
Console.WriteLine (m1);   // color
Console.WriteLine (m2);   // colours

foreach (Match x in Regex.Matches
          ("One color? There are two colours in my head!", @"colou?rs?"))
  Console.WriteLine (x);

Alternator با | گزینه‌های جایگزین را بیان می‌کند. برای مثال:

Console.WriteLine (Regex.IsMatch ("Jenny", "Jen(ny|nifer)?"));  // True

Parenthesis اطراف alternator، alternativeها را از بقیهٔ expression جدا می‌کند.

Compiled Regular Expressions

اگر pattern یکسان را بارها با static methodهای Regex استفاده می‌کنید، می‌توان یک Regex instance با RegexOptions.Compiled ساخت:

Regex r = new Regex (@"sausages?", RegexOptions.Compiled);
Console.WriteLine (r.Match ("sausage"));
Console.WriteLine (r.Match ("sausages"));

Compiled از code generation سبک ــ DynamicMethod در Reflection.Emit ــ برای ساخت و compile کد ویژهٔ همان pattern استفاده می‌کند؛ matching سریع‌تر می‌شود اما هزینهٔ اولیهٔ compilation دارد. Regex instance بدون Compiled هم ممکن است و instanceها immutable هستند. Engine حتی بدون compilation نیز سریع است و simple match معمولاً کمتر از یک microsecond طول می‌کشد.

RegexOptions

enum پرچمی RegexOptions رفتار matching را تغییر می‌دهد. نمونهٔ رایج، ignore case است:

Console.WriteLine (Regex.Match ("a", "A", RegexOptions.IgnoreCase));
Console.WriteLine (Regex.Match ("a", "A", RegexOptions.IgnoreCase |
                                        RegexOptions.CultureInvariant));

بیشتر optionها را می‌توان با code تک‌حرفی داخل expression فعال یا غیرفعال کرد:

Console.WriteLine (Regex.Match ("a", @"(?i)A"));
Console.WriteLine (Regex.Match ("AAAa", @"(?i)a(?-i)a"));

IgnorePatternWhitespace یا (?x) whitespace بدون escape را از pattern نادیده می‌گیرد و خوانایی expression پیچیده را بیشتر می‌کند. NonBacktracking از .NET 7 یک الگوریتم forwards-only می‌خواهد؛ معمولاً کندتر است و قابلیت‌هایی مثل lookahead/lookbehind را غیرفعال می‌کند، اما time complexity قابل پیش‌بینی‌تری دارد و در برابر ReDOS مفید است.

Table 25-1 ـ Regular expression options
EnumCodeمعنی
Noneرفتار پیش‌فرض
IgnoreCaseiنادیده‌گرفتن case
Multilinem^ و $ را برای ابتدا/انتهای line تفسیر می‌کند
ExplicitCapturenفقط groupهای صریحاً named یا numbered را capture می‌کند
CompiledCompile به IL
Singlelines. را با همهٔ characterها match می‌کند
IgnorePatternWhitespacexWhitespace بدون escape را از pattern حذف می‌کند
RightToLeftrجست‌وجو از راست به چپ
ECMAScriptوادارکردن به ECMA compliance
CultureInvariantخاموش‌کردن رفتار وابسته به culture در string comparison
NonBacktrackingغیرفعال‌کردن backtracking برای performance قابل پیش‌بینی‌تر

Character Escapeها

Metacharacterهای regex معنای ویژه دارند:

\ * + ? | { [ () ^ $ . #

برای استفادهٔ literal از metacharacter معمولاً آن را با backslash escape می‌کنیم:

Console.WriteLine (Regex.Match ("what?", @"what\?")); // what?
Console.WriteLine (Regex.Match ("what?", @"what?"));  // what

داخل character set یا square bracket، این قاعده برای بسیاری از metacharacterها اعمال نمی‌شود و آن‌ها literal تفسیر می‌شوند. متدهای Regex.Escape و Regex.Unescape نیز metacharacterها را به فرم escaped و برعکس تبدیل می‌کنند.

نمونه‌های کتاب از verbatim string در C# یعنی prefixِ @ استفاده می‌کنند تا escape mechanism خود C# با backslashهای regex تداخل نداشته باشد. بدون @، یک backslash literal ممکن است به چهار backslash در source نیاز داشته باشد. مگر اینکه (?x) فعال باشد، space نیز literal است.

Character Setها

Character setها wildcard برای مجموعه‌ای از characterها هستند:

ExpressionمعنیInverse
[abcdef]یک character از list[^abcdef]
[a-f]یک character از range[^a-f]
\dUnicode digit؛ در ECMAScript تقریباً [0-9]\D
\wword character؛ وابسته به culture\W
\swhitespace مطابق char.IsWhiteSpace\S
\p{category}character در Unicode category مشخص\P
.در حالت پیش‌فرض هر character جز newline؛ در Singleline همهٔ characterها
Console.Write (Regex.Matches ("That is that.", "[Tt]hat").Count);   // 2
Console.Write (Regex.Match ("quiz qwerty", "q[^aeiou]").Index);    // 5

Hyphen داخل set برای range استفاده می‌شود. Unicode categoryها امکان match کردن letter، number، punctuation، mark، symbol، separator و control character را بدون list کردن تک‌تک characterها می‌دهند.

Quantifierها

Quantifierمعنی
*صفر یا بیشتر
+یک یا بیشتر
?صفر یا یک
{n}دقیقاً n بار
{n,}حداقل n بار
{n,m}بین n و m بار

برای filenameهایی مانند cv.docx می‌توان patternهایی ساخت که تعداد occurrenceها را محدود کنند؛ period در extension باید با backslash escape شود.

Greedy در برابر Lazy

Quantifierها به‌طور پیش‌فرض greedy هستند: تا حد ممکن character مصرف می‌کنند و سپس در صورت نیاز backtrack می‌کنند. با suffixِ ? quantifier lazy می‌شود و تا حداقل مقدار لازم مصرف می‌کند. برای مثال .*? در استخراج چند fragment مجزا معمولاً از .* مناسب‌تر است.

Zero-Width Assertionها

Regex می‌تواند شرطی را دربارهٔ موقعیت فعلی بررسی کند بدون اینکه characterی مصرف کند. Lookahead سمت راست و lookbehind سمت چپ را می‌سنجد. Positive lookahead با (?=expr) و negative با (?!expr) است؛ positive lookbehind با (?<=expr) و negative با (?<!expr).

پس از lookahead موفق، matching طوری ادامه پیدا می‌کند که گویی preview رخ نداده است. کاربرد نمونه، password rule است: ابتدا با lookahead بررسی کنید جایی در string digit یا symbol وجود دارد، سپس طول کل را match کنید.

Anchorها

Expressionمعنی
^ابتدای string یا line در multiline
$انتهای string یا line در multiline
\Aابتدای string مستقل از multiline
\zانتهای string مستقل از multiline
\Zانتهای line یا string
\Gمحل شروع search

Anchor یک position را match می‌کند نه character را. Word boundary با \b و non-boundary با \B بیان می‌شود.

Groupها

Parenthesis علاوه بر precedence، subexpression را capture می‌کند. Group صفر کل match است. Groupهای دیگر از collectionِ Groups قابل خواندن‌اند و در خود regex نیز با back-reference قابل استفاده‌اند. Noncapturing group با (?:expr) ساخته می‌شود.

Named Groupها

در expressionهای طولانی، نام‌گذاری group خوانایی را بیشتر می‌کند. Syntaxهایی مانند (?'name'expr) یا فرم زاویه‌براکت برای capture و \k'name'/\k<name> برای back-reference استفاده می‌شوند. مثال سادهٔ XML/HTML می‌تواند نام tag شروع را capture و برای tag پایان دوباره استفاده کند.

جایگزینی و تقسیم متن

Regex.Replace مانند string.Replace است اما pattern می‌پذیرد. Replacement می‌تواند از groupها استفاده کند: $0 کل match، $1 group شماره‌دار و ${name} named group را جایگزین می‌کند.

Overload دیگری از Replace یک MatchEvaluator می‌گیرد که برای هر match اجرا می‌شود. این روش زمانی مفید است که replacement باید از calculation یا transformation هر match ساخته شود، مثلاً escape کردن Unicode characterها برای HTML.

Regex.Split نسخهٔ قدرتمندتر string.Split است و delimiter را به‌صورت regex pattern می‌پذیرد.

Cookbook: الگوهای آماده

US Social Security Number و Phone Number

Regex می‌تواند قالب‌های شماره را با digitها، separator اختیاری و parenthesis کنترل کند. نمونهٔ phone number منبع:

string phone = @"(?x)
    ( \(\d{3}\) | \d{3})
    [\s-]?
    \d{3}[-\s]?
    \d{4}";
Console.WriteLine (Regex.IsMatch ("123-456-7890",   phone));   // True
Console.WriteLine (Regex.IsMatch ("(123) 456-7890", phone));   // True

استخراج pairهای name = value

string r = @"(?m)^\s*(?'name'\w+)\s*=\s*(?'value'.*)\s*(?=\r?$)";

این pattern با multiline directive هر line را پردازش می‌کند و groupهای name و value را می‌دهد.

اعتبارسنجی Password قوی

string r = @"(?x)^(?=.* ( \d | \p{P} | \p{S} )).{6,}";
Console.WriteLine (Regex.IsMatch ("abc12", r));     // False
Console.WriteLine (Regex.IsMatch ("abcdef", r));    // False
Console.WriteLine (Regex.IsMatch ("ab88yz", r));    // True

این expression حداقل شش character و وجود حداقل یک digit، punctuation یا symbol را کنترل می‌کند.

Lineهای حداقل 80 character

string r = @"(?m)^.{80,}(?=\r?$)";

Parsing date/time عددی

string r = @"(?x)(?i)
 (\d{1,4}) [./-]
 (\d{1,2}) [./-]
 (\d{1,4}) [\sT]
 (\d+):(\d+):(\d+) \s? (A\.?M\.?|P\.?M\.?)?";

این pattern قالب‌های گوناگون عددی date/time را می‌پذیرد، چه year اول باشد چه آخر. البته صحت تقویمی date/time را validate نمی‌کند.

Roman Numeral

string r =
  @"(?i)\bm*"         +
  @"(d?c{0,3}|c[dm])" +
  @"(l?x{0,3}|x[lc])" +
  @"(v?i{0,3}|i[vx])" +
  @"\b";

حذف word تکراری

string r = @"(?'dupe'\w+)\W\k'dupe'";
string text = "In the the beginning...";
Console.WriteLine (Regex.Replace (text, r, "${dupe}"));
// In the beginning

Word Count

string r = @"\b(\w|[-'])+\b";
string text = "It's all mumbo-jumbo to me";
Console.WriteLine (Regex.Matches (text, r).Count);   // 5

GUID

string r =
  @"(?i)\b"           +
  @"[0-9a-fA-F]{8}\-" +
  @"[0-9a-fA-F]{4}\-" +
  @"[0-9a-fA-F]{4}\-" +
  @"[0-9a-fA-F]{4}\-" +
  @"[0-9a-fA-F]{12}"  +
  @"\b";

Parsing یک XML/HTML Tag

string r =
  @"<(?'tag'\w+?).*>" +
  @"(?'text'.*?)"      +
  @"</\k'tag'>";

این pattern برای fragmentهای HTML ــ حتی وقتی document کاملاً well-formed نیست ــ مفید است؛ group tag نام tag و group text محتوا را capture می‌کند.

تقسیم camelCase

string r = @"(?=[A-Z])";
foreach (string s in Regex.Split ("oneTwoThree", r))
  Console.Write (s + " ");    // one Two Three

ساخت filename قانونی

string input = "My \"good\" <recipes>.txt";
char[] invalidChars = System.IO.Path.GetInvalidFileNameChars();
string invalidString = Regex.Escape (new string (invalidChars));
string valid = Regex.Replace (input, "[" + invalidString + "]", "");
Console.WriteLine (valid);     // My good recipes.txt

Escape کردن Unicode برای HTML

string htmlFragment = "© 2007";
string result = Regex.Replace (htmlFragment, @"[\u0080-\uFFFF]",
                m => @"&#" + ((int)m.Value[0]).ToString() + ";");

Unescape کردن HTTP Query String

string sample = "C%23 rocks";
string result = Regex.Replace (
    sample,
    @"%[0-9a-f][0-9a-f]",
    m => ((char) Convert.ToByte (m.Value.Substring (1), 16)).ToString(),
    RegexOptions.IgnoreCase
);

Parsing عبارت‌های Google از Web Log

string sample =
  "http://google.com/search?hl=en&q=greedy+quantifiers+regex&btnG=Search";
Match m = Regex.Match (sample, @"(?<=google\..+search\?.*q=).+?(?=(&|$))");
string[] keywords = m.Value.Split (
  new[] { '+' }, StringSplitOptions.RemoveEmptyEntries);

در منبع توصیه شده این نمونه با unescape کردن characterهای query string ترکیب شود.

مرجع زبان Regular Expression

جدول‌های 25-2 تا 25-12 grammar و syntax پشتیبانی‌شده در implementation .NET را خلاصه می‌کنند.

Table 25-2 ـ Character Escapeها

EscapeمعنیHex
\aBellU+0007
\bBackspaceU+0008
\tTabU+0009
\rCarriage returnU+000A
\vVertical tabU+000B
\fForm feedU+000C
\nNewlineU+000D
\eEscapeU+001B
\nnnASCII به octal
\xnnASCII به hex
\clASCII control character
\unnnnUnicode character با hex
\symbolSymbol با escape

Table 25-3 و 25-4 ـ Character Set و Category

Categoryمعنی
\p{L}Letterها
\p{Lu}Uppercase letter
\p{Ll}Lowercase letter
\p{N}Numberها
\p{P}Punctuation
\p{M}Diacritic mark
\p{S}Symbolها
\p{Z}Separatorها
\p{C}Control characterها

Table 25-5 ـ Quantifierها

همان *، +، ?، {n}، {n,} و {n,m} هستند. suffixِ ? هر quantifier را از greedy به lazy تبدیل می‌کند.

Table 25-6 ـ Substitutionها

Expressionمعنی
$0متن match‌شده
$group-numbergroup شماره‌دار
${group-name}group نام‌دار

Substitution فقط در replacement pattern مشخص می‌شود.

Table 25-7 ـ Zero-Width Assertionها

Expressionمعنی
^ابتدای string یا line
$انتهای string یا line
\Aابتدای string
\zانتهای string
\Zانتهای line یا string
\Gمحل آغاز search
\bروی word boundary
\Bخارج word boundary
(?=expr)Positive lookahead
(?!expr)Negative lookahead
(?<=expr)Positive lookbehind
(?<!expr)Negative lookbehind
(?>expr)Atomic subexpression؛ یک بار match و بدون backtrack

Table 25-8 ـ Grouping Constructها

Syntaxمعنی
(expr)Capture در indexed group
(?number)Capture در group number مشخص
(?'name')Capture در named group
(?'name1-name2')Undefine کردن name2 و ذخیرهٔ interval/current group در name1؛ اگر name2 تعریف نشده باشد backtrack می‌کند
(?:expr)Noncapturing group

Table 25-9 ـ Back Referenceها

Syntaxمعنی
\indexReference به group قبلاً capture‌شده بر اساس index
\k<name>Reference به group بر اساس name

Table 25-10 ـ Alternation

Syntaxمعنی
|Logical OR
(?(expr)yes|no)اگر expr match شد yes وگرنه no؛ no اختیاری است
(?(name)yes|no)اگر named group match داشته باشد yes وگرنه no

Table 25-11 ـ Constructهای متفرقه

Syntaxمعنی
(?#comment)Inline comment
#commentComment تا انتهای line، فقط در IgnorePatternWhitespace

Table 25-12 ـ Optionهای داخل Expression

Optionمعنی
(?i)Case-insensitive
(?m)Multiline؛ تغییر معنای ^ و $
(?n)فقط capture صریح named/numbered
(?c)Compile به Intermediate Language
(?s)Single-line؛ . همه characterها را match می‌کند
(?x)حذف whitespace بدون escape از pattern
(?r)Search از راست به چپ؛ midstream قابل تعیین نیست

فروش یا انتشار این ترجمه منوط به داشتن مجوز لازم از صاحب حقوق اثر است.

این مقاله بخشی از ترجمهٔ پیوستهٔ C# 12 in a Nutshell است و برای ناوبری مجموعه به مقالهٔ مادر متصل شده است.

امتیاز کاربران به این مقاله

☆☆☆☆☆

0 نفر امتیاز داده اند. میانگین: 0.0 از 5

 

0 نظر

نظر محترم شما در مورد مقاله های وب سایت برنامه نویسی و پایگاه داده

نظرات محترم شما در خدمات رسانی بهتر ما را یاری می نمایند. لطفا اگر مایل بودید یک نظر ما را مهمان فرمائید. آدرس ایمیل و وب سایت شما نمایش داده نخواهد شد.

0 / 500

اطلاعات تماس

  • آدرس:اصفهان-خیابان ام کلثوم غربی - بعد خیابان تخم چی - بیست متر بعد از پیتزا ننه شب - کوچه تعمیر گاه سمار زغالی - پلاک 354 - درب مشکی - طبقه هفتم
  • آدرس ایمیل:najafzade@gmail.com
  • وب سایت:http://www.a00b.com/
  • تلفن ثابت:(+98)9131253620
  • تلفن همراه:09131253620