آشنایی با عبارات منظم و شئ RegExp
در آخرین بخش از فصل پنجم این کتاب، قصد داریم به بررسی مفهوم عبارات منظم (Regular Expressions) و کاربرد آنها بپردازیم. همچنین شئ RegExp را نیز معرفی خواهیم کرد که برای کار با عبارات منظم در جاوا اسکریپت در نظر گرفته شده است. قبل از شروع بحث باید به این نکته اشاره شود که مبحث عبارات منظم، مبحث بسیار گستردهای است. و برای پوشش تمام جنبههای این بحث نیاز به یک کتاب جداگانه است. در واقع مبحث عبارات منظم، یک مبحث مشترک در اکثر زبانهای برنامهنویسی است و منابع آموزشی زیادی در رابطه با این موضوع، بدون در نظر گرفتن زبان برنامهنویسی خاصی وجود دارد. در این بخش صرفاً به معرفی مفاهیم اولیهی عبارات منظم و نحوهی استفاده از آنها در جاوا اسکریپت خواهیم پرداخت. علاقهمندان میتوانند جهت کسب اطلاعات بیشتر به منابع آموزشی ویژهی عبارات منظم مراجعه کنند.
مفهوم عبارات منظم
در فصل دوم با رشتهها آشنا شدیم و با برخی از متدهای مرتبط با رشتهها مانند متدهای indexOf و includes که برای جستجو در رشتهها به کار میروند آشنا شدیم. عبارات منظم روش دیگری برای جستجو و جایگزینی در رشتهها هستند که انعطافپذیری و قدرت بسیار بیشتری نسبت به روشهای قبلی دارند. به عنوان مثال در قطعه کد زیر با استفاده متد indexOf محل وقوع کلمهی "JavaScript" مورد جستجو قرار گرفته است.
let str = 'Hello, welcome to JavaScript!';
str.indexOf('JavaScript');
← 18
اما این روش انعطافپذیری بسیار کمی دارد. مثلاً فرض کنید میخواهیم محل وقوع اولین کلمهی ۵ حرفی و یا محل وقوع اولین عدد ۴ رقمی را پیدا کنیم. متد indexOf و سایر متدهایی که پیش از این معرفی شدهاند، امکان انجام چنین جستجویی را ندارند. اما با استفاده از عبارات منظم به راحتی میتوان اینگونه جستجوها را انجام داد. قبل از این که با نحوهی استفاده از عبارات منظم آشنا شویم، لازم است تا با نحوهی تعریف آنها آشنا شویم.
برای تعریف یک عبارت منظم در جاوا اسکریپت دو روش وجود دارد. مانند روشهای مختلف تعریف آرایهها و اشیاء، برای تعریف عبارات منظم نیز میتوان از فرم Literal و همچنین از تابع سازندهی RegExp استفاده کرد. قطعه کد زیر نحوهی تعریف یک عبارت منظم واحد را به دو روش ذکر شده نشان میدهد.
let reg1 = /javascript/i;
let reg2 = new RegExp('javascript' , 'i');
هر عبارت منظم از دو قسمت الگو (Pattern) و پرچمها (flags) تشکیل میشود. در کد فوق هر دو متغیر reg1 و reg2 عبارت منظم یکسانی را ایجاد میکنند که در هر دو مورد الگو برابر با "javascript" و پرچمها برابر با "i" میباشد. در اکثر موارد از فرم Literal برای تعریف عبارات منظم استفاده میشود. در این روش ابتدا بخش الگو داخل یک جفت کاراکتر "/" قرار گرفته و در ادامه پرچمها قرار میگیرند. توجه کنید که بخش پرچمها اختیاری است و ممکن است در برخی عبارات منظم از پرچمها استفاده نشود. اما بخش الگو اجباری است.
اما روش دوم که استفاده از تابع سازندهی RegExp است، یک مزیت نسبت به روش اول دارد. در این روش میتوان هم در قسمت الگو و هم در قسمت پرچمها از متغیرها استفاده کرد. پس اگر عبارت منظم از پیش تعیین شده نباشد و در زمان اجرای برنامه ساخته شود، حتماً باید از روش دوم استفاده شود. همچنین در صورتی که آرگومانهای ورودی تابع RegExp مانند مثال فوق به صورت رشته تعریف شوند، لازم است به ازای هر کاراکتر "\"، از دو کاراکتر "\" استفاده شود. کاراکتر "\" کاربرد زیادی در بخش الگوی عبارات منظم دارد. در روش اول نیازی به Escape کردن این کاراکتر نیست. اما در روش دوم باید این کاراکتر را با قرار دادن یک کاراکتر "\" اضافی Escape کنیم.
قسمت الگو (Pattern) در عبارات منظم
قسمت مهم و اصلی یک عبارت منظم، الگوی آن است. در قسمت الگو تعیین میشود که دقیقاً به دنبال چه چیزی هستیم. ابتدا با یک مثال بسیار ساده شروع میکنیم. قطعه کد زیر یک عبارت منظم برای جستجوی عبارت "javascript" تعریف میکند.
let reg = /javascript/;
حال با استفاده از متد test میتوان بررسی کرد که این عبارت منظم با یک رشتهی خاص مطابقت دارد یا خیر؟ یعنی آیا رشتهی مورد نظر حاوی زیر رشتهی "javascript" هست یا خیر؟ خروجی این متد true یا false است.
reg.test('Hello, welcome to JavaScript!');
← false
reg.test('Hello, welcome to javascript!');
← true
مشاهده میکنید که در حالت پیشفرض بین حروف بزرگ و کوچک تفاوت وجود دارد. اما میتوان با استفاده از پرچمها این وضعیت را تغییر داد. حال به یک مثال کمی پیچیدهتر میپردازیم.
فرض کنید به یک عبارت منظم نیاز داریم که با رشتهای که حاوی تعداد مشخصی از یک کاراکتر به صورت متوالی است مطابقت کند. برای مشخص کردن تعداد تکرارهای یک کاراکتر میتوان از آکلاد استفاده کرد. مثلاً عبارات منظم زیر با هر رشتهای که به ترتیب ۲ کاراکتر "l" به صورت متوالی و یا ۵ کاراکتر "l" به صورت متوالی داشته باشند مطابقت میکنند.
let reg = /l{2}/;
reg.test('Hello, welcome to JavaScript!');
← true
reg = /l{5}/;
reg.test('Hello, welcome to JavaScript!');
← false
با توجه به این که در کلمهی "Hello" دو بار کاراکتر "l" به صورت متوالی تکرار شده است. نتیجهی متد test اول true است. اما کاراکتر "l" در هیچ بخشی از رشتهی فوق ۵ بار به صورت متوالی تکرار نشده، بنابراین خروجی متد test دوم false است.
حال کمی جلوتر میرویم و عبارت منظمی میسازیم که با رشتهای ۳ حرفی که کاراکتر اول آن "c" و کاراکتر سوم آن "m" باشد مطابقت کند. توجه کنید که کاراکتر دوم رشته هیچ اهمیتی ندارد و فقط کاراکتر اول و سوم مهم هستند. پس به یک کاراکتر خاص در عبارت منظم نیاز داریم که با هر کاراکتری در رشتهی مورد نظر مطابقت داشته باشد. در عبارات منظم میتوان از کاراکتر نقطه برای این منظور استفاده کرد. قطعه کد زیر نحوهی عملکرد کاراکتر نقطه در عبارات منظم را نشان میدهد.
let reg = /c.m/;
reg.test('Hello, welcome to JavaScript!');
← true
reg.test('I love JavaScript!');
← false
با توجه به این که در رشتهی اول زیر رشتهی "com" وجود دارد. و در این زیر رشته حرف اول "c" و حرف سوم "m" است. بدون توجه به حرف دوم، مطابقت با عبارت منظم صورت میگیرد. توجه کنید که لزومی ندارد که عبارت منظم با کل رشته مطابقت داشته باشد. بلکه هر بخشی از رشته میتواند با عبارت منظم مطابقت داشته باشد و در صورت تطابق، مقدار true بازگردانده میشود. اما در رشتهی دوم هیچ بخشی از رشته با عبارت منظم مطابقت ندارد. لذا مقدار false بازگردانده میشود.
قسمت پرچمها (Flags) در عبارات منظم
با استفاده از پرچمها میتوان کنترل بیشتری بر روی نحوهی عملکرد عبارات منظم اعمال کرد. یکی از مهمترین پرچمها در عبارات منظم پرچم "i" است. در صورت استفاده از این پرچم، تفاوتی بین حروف بزرگ و کوچک وجود نخواهد داشت. قطعه کد زیر یکی از مثالهای قبلی را نشان میدهد که با افزودن پرچم "i" اصلاح شده است.
let reg = /javascript/i;
reg.test('Hello, welcome to JavaScript!');
← true
reg.test('Hello, welcome to javascript!');
← true
مشاهده میکنید که دیگر تفاوتی بین حروف بزرگ و کوچک وجود ندارد و هر دو رشته با عبارت منظم مطابقت میکنند.
انواع دیگری از پرچمها در عبارات منظم وجود دارد. اما در این بخش به معرفی سایر موارد نمیپردازیم. زیرا همانطور که اشاره شد هدف از این بخش آموزش عبارات منظم نیست. بلکه هدف از این بخش معرفی امکانات جاوا اسکریپت در رابطه با عبارات منظم است. البته در ادامه به پرچم "g" نیز اشاره خواهد شد. بنابراین علاقهمندان جهت کسب اطلاعات بیشتر در مورد عبارات منظم میتوانند به منابع آموزشی ویژهی عبارات منظم مراجعه کنند.
متد exec
شئ RegExp متد دیگری به نام exec دارد. این متد نیز مانند متد test برای بررسی تطابق یک عبارت منظم با یک رشته به کار برده میشود. با این تفاوت که خروجی این متد true یا false نیست. بلکه مشخصات کامل بخشی از رشته که با عبارت منظم مطابقت دارد را به صورت یک آرایه بازمیگرداند. برای نمونه، مثال قبلی را این بار با استفاده از متد exec اجرا میکنیم.
let reg = /javascript/i;
reg.exec('Hello, welcome to JavaScript!');
← ["JavaScript", index: 18, input: "Hello, welcome to JavaScript!"]
reg.exec('Hello, welcome to javascript!');
← ["javascript", index: 18, input: "Hello, welcome to javascript!"]
آرایهای که توسط این متد بازگردانده میشود، با آرایههایی که پیش از این دیدهایم کمی متفاوت است. این آرایه دارای دو خاصیت index و input است که به ترتیب محل وقوع تطابق و رشتهی ورودی را در خود نگهداری میکنند. اولین عنصر این آرایه نیز، زیر رشتهی مطابقت یافته را نگهداری میکند.
همچنین در صورتی که در رشتهای بیش از یک تطابق با یک عبارت منظم وجود داشته باشد، با تکرار متد exec تطابقهای بعدی را نیز میتوان یافت. البته برای استفاده از این قابلیت حتماً باید از پرچم "g" نیز در عبارت منظم استفاده شود. به قطعه کد زیر توجه کنید.
let reg = /.a/ig;
reg.exec('Hello, welcome to JavaScript!');
← ["Ja", index: 18, input: "Hello, welcome to JavaScript!"]
reg.exec('Hello, welcome to JavaScript!');
← ["va", index: 20, input: "Hello, welcome to JavaScript!"]
این عبارت منظم با کاراکتر "a" و کاراکتر قبلی آن مطابقت میکند. یعنی در هر نقطهای از رشته که کاراکتر "a" وجود داشته باشد، این کاراکتر به همراه کاراکتر قبلی آن با عبارت منظم مطابقت خواهند داشت. در نتیجه تنها حالتی که کاراکتر "a" با این عبارت منظم مطابقت ندارد، حالتی است که دقیقاً اولین کاراکتر رشته باشد. زیرا در این صورت هیچ کاراکتری قبل از آن نیست که با نقطه مطابقت کند. همانطور که مشاهده میکنید در رشتهی فوق دو بار کاراکتر "a" وجود دارد که با دو بار اجرای متد exec هر دو مورد یافت میشوند. توجه کنید که برای استفاده از این قابلیت، استفاده از پرچم "g" در عبارت منظم ضروری است.
رشتهها و عبارات منظم
در فصل دوم با متغیرهای رشتهای و برخی متدهای مرتبط با آنها آشنا شدیم. برخی از متدهای مرتبط با رشتهها قابلیت کار با عبارات منظم را نیز دارند. مثلاً متد split که برای تجزیهی یک رشته به کار میرود را در نظر بگیرید. این متد میتواند یک رشته را بر اساس یک زیر رشتهی خاص به چند قسمت تقسیم کند. این زیر رشتهی خاص را هم میتوان به صورت یک رشتهی ساده تعریف کرد و هم میتوان به صورت یک عبارت منظم تعریف کرد. قطعه کد زیر نحوهی عملکرد این متد را در هر دو حالت نشان میدهد.
let str = 'Hello, welcome to JavaScript!';
str.split(' '); // تجزیه بر اساس فاصله
← ["Hello,", "welcome", "to", "JavaScript!"]
str.split(','); // تجزیه بر اساس کاراکتر کاما
← ["Hello", " welcome to JavaScript!"]
str.split(/[ ,]/); // تجزیه بر اساس فاصله یا کاما با یک عبارت منظم
← ["Hello", "", "welcome", "to", "JavaScript!"]
در این مثال در سومین متد split از یک عبارت منظم برای تجزیه استفاده شده است. این عبارت منظم شامل یک جفت براکت است که داخل آن دو کاراکتر فاصله و کاما قرار گرفته است. در عبارات منظم، براکت به معنی "OR منطقی" است. یعنی هر براکت میتواند با یکی از کاراکترهای داخل مجموعه (کاما یا فاصله) مطابقت کند.
با استفاده از متد match نیز میتوان بخشهایی از یک رشته را که با یک عبارت منظم مطابقت دارند انتخاب کرد. این متد یک عبارت منظم را دریافت کرده و یک آرایه که شامل زیر رشتههای مطابقت یافته است را بازمیگرداند.
let str = 'JavaScript!';
str.match(/[aeiou]/);
← ["a"]
با توجه به قرار دادن کاراکترهای "aeiou" در براکت، هر یک از این کاراکترها در رشتهی مورد نظر، میتواند با عبارت منظم مطابقت کند. در ۳ نقطه از رشتهی فوق از این کاراکترها استفاده شده است. اما مشاهده میکنید که فقط اولین تطابق صورت گرفته بازگردانده شده است. در متد match نیز مانند متد exec برای این که کلیهی موارد تطابق یافته بازگردانده شوند، لازم است از پرچم "g" استفاده شود.
let str = 'JavaScript!';
str.match(/[aeiou]/g);
← ["a", "a", "i"]
مشاهده میکنید که این بار یک آرایه با سه عنصر بازگردانده میشود که شامل هر سه مورد تطابق یافته با عبارت منظم است.
با استفاده از متد search نیز میتوان محل وقوع اولین تطابق با یک عبارت منظم را در یک رشته جستجو کرد. در صورتی که هیچ تطابقی یافت نشود، این متد مقدار 1- را بازمیگرداند.
let str = 'JavaScript!';
str.search(/[aeiou]/);
← 1
str.search(/ruby/);
← -1
و اما آخرین و شاید مهمترین متدی که در این بخش معرفی میکنیم متد replace است. این متد میتواند بخشهایی از یک رشته را که با یک عبارت منظم مطابقت دارند، با رشتهای دیگر جایگزین کند. نمونههایی از کاربرد این متد در قطعه کد زیر نشان داده شده است.
let str = 'JavaScript!';
str.replace(/[aeiou]/ , '*');
← "J*vaScript!"
str.replace(/[aeiou]/g , '*');
← "J*v*Scr*pt!"
باز هم مشاهده میکنید که برای جایگزینی تمام موارد تطابق، باید از پرچم "g" استفاده شود.
مثالهایی که در این بخش نشان داده شدند، مثالهای بسیار سادهای از عبارات منظم بودند. عبارات منظم قدرت بسیار بیشتری دارند و کارهای بسیار پیچیدهتری را میتوان با عبارات منظم در جاوا اسکریپت (و سایر زبانهای برنامهنویسی) انجام داد. ارائهی یک بحث کامل در مورد عبارات منظم خارج از حوزهی این کتاب است. اما صرفاً به عنوان نمونه، چند مورد از کاربردهای عبارات منظم، بدون تشریح اجزاء تشکیل دهندهی عبارت منظم ارائه میشود.
- تشخیص معتبر بودن قالب کد ملی
let regex = /^\d{3}-\d{6}-\d$/;
regex.test('008-249981-3');
← true
regex.test('0b8-249c81-3');
← false
regex.test('00-8249981-3');
← false
- تشخیص معتبر بودن یک ایمیل
let regex = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,6})+$/;
regex.test('abbassac@gmail.com');
← true
regex.test('omid256@domain.org');
← true
regex.test('abbassac#@gmail.com');
← false
- تشخیص معتبر بودن یک عدد (استفاده از کاما و نقطه در اعداد، به صورت صحیح آن مجاز است)
let regex = /^(((\d{1,3})(,\d{3})*)|(\d+))(.\d+)?$/;
regex.test('9999999');
← true
regex.test('99999.99999');
← true
regex.test('99,999,999.9999');
← true
regex.test('9999.');
← false
regex.test('9,99,99999.99');
← false