প্যাটার্নের কোন একটা অংশকে প্যারেন্টেসিস pattern:(...)
দ্বারা লিখাকে "গ্রুপ ক্যাপচারিং" বলে।
এর ফলে ফলাফলে দুটি পরিবর্তন আছে:
১. এটি দ্বারা লব্ধ ফলাফলের অ্যারেতে কোন একটা অংশকে আলাদা পজিশনে রাখা যায়। ২. যদি প্যারেন্টেসিসের পরে আমরা কোয়ান্টিফায়ার রাখি, এটি সম্পূর্ন গ্রুপের জন্য কাজ করবে।
চলুন উদাহরণ দিয়ে দেখি গ্রুপ ক্যাপচারিং কিভাবে কাজ করে।
প্যারেন্টেসিস ছাড়া, এই প্যাটার্নটি pattern:go+
দ্বারা বুঝায় subject:g
এবং subject:o
এই দুটি ক্যারাক্টার এক বা একাধিকবার পুনরাবৃত্তি হবে। উদাহরণস্বরুপ, match:goooo
অথবা match:gooooooooo
।
প্যারেন্টেসিস দ্বারা গ্রুপ ক্যারাক্টারগুলো একসাথে বুঝায়, সুতরাং pattern:(go)+
দ্বারা প্রাপ্তমিলগুলো হতে পারে match:go
, match:gogo
, match:gogogo
অনুরূপ আরো অনেক।
alert( 'Gogogo now!'.match(/(go)+/ig) ); // "Gogogo"
চলুন আরো কঠিন কিছু করি -- রেগুলার এক্সপ্রেশন দ্বারা ওয়েবসাইটের ডোমেন খুঁজে বের করা।
যেমন:
mail.com
users.mail.com
smith.users.mail.com
এইক্ষেত্রে আমরা দেখছি, ডোমেনে শেষ শব্দটি ব্যতীত প্রতিটি শব্দের শেষে একটি ডট থাকবে।
রেগুলার এক্সপ্রেশন আমরা এটি এভাবে লিখতে পারি pattern:(\w+\.)+\w+
:
let regexp = /(\w+\.)+\w+/g;
alert( "site.com my.site.com".match(regexp) ); // site.com,my.site.com
এটি কাজ করছে, কিন্তু উপরের প্যাটার্নটি হাইফেনসহ ডোমেনের জন্য সঠিকভাবে কাজ করবে না, যেমন. my-site.com
, কেননা pattern:\w
এই ক্যারাক্টার ক্লাস দ্বারা হাইফেনকে নির্দেশিত করা যায় না।
pattern:\w
এর পরিবর্তে pattern:[\w-]
লিখার মাধ্যমে আমরা এটিকে নির্ভুল করতে পারি সুতরাং প্যাটার্নটি হবে: pattern:([\w-]+\.)+\w+
।
পূর্ববর্তী উদাহরণটিকে কিছুটা বর্ধিত করে আমরা ইমেইল এর জন্য একটি রেগুলার এক্সপ্রেশন লিখতে পারি।
ইমেইল এর ফরম্যাট: name@domain
। নামের মধ্যে ডট হাইফেন ইত্যাদি থাকতে পারে। সুতরাং রেগুলার এক্সপ্রেশনটি হবে pattern:[-.\w]+
।
প্যাটার্ন:
let regexp = /[-.\w]+@([\w-]+\.)+[\w-]+/g;
alert("[email protected] @ [email protected]".match(regexp)); // my@mail.com, [email protected]
এটি পুরোপুরি নির্ভুল নই, কিন্তু বেশিরভাগক্ষেত্রে অ্যাক্সিডেন্টালি ভুল টাইপিং এড়ানোর জন্য কাজের। আমরা একটি ইমেইল নির্ভুল কিনা তা যাচাই করতে পারি শুধুমাত্র ইমেইল প্রেরণের মাধ্যমে।
প্যারেন্টেসিসের কন্টেন্ট গুলোকে বাম থেকে ডানে হিসেব করা হয়। সার্চ ইঞ্জিন মিলকৃত সকল কন্টেন্টকে মনে রাখে এবং রেজাল্টে এদের পাওয়া যায়।
str.match(regexp)
মেথড, যদি regexp
কোন g
ফ্ল্যাগ না থাকে, তাহলে প্রথম মিলটি খুঁজে এবং এটি অ্যারে হিসেবে দেখায়:
১. 0
তম ইনডেক্সে: সম্পূর্ণ মিলটি।
২. 1
তম ইনডেক্সে: প্রথম প্যারেন্টেসিসের মিলগুলো।
৩. 2
তম ইনডেক্সে: দ্বিতীয় প্যারেন্টেসিসের মিলগুলো।
৪. ...এভাবেই চলতে থাকে...
উদাহরণস্বরূপ, আমরা HTML ট্যাগ pattern:<.*?>
খুঁজে পেতে চাই, এবং এদের নিয়ে কাজ করতে চাই। এজন্য আমাদের ট্যাগগুলো এবং ট্যাগের নাম গুলো আলাদা আলাদা ভ্যারিয়েবলে রাখা সুবিধাজনক।
চলুন ট্যাগ নামগুলোকে আমরা প্যারেন্টেসিসের দ্বারা আবদ্ধ করি, এভাবে: pattern:<(.*?)>
।
এখন আমরা পুরো ট্যাগটি match:<h1>
এবং ট্যাগ নামটি match:h1
রেজাল্টে অ্যারে হিসেবে পাব:
let str = '<h1>Hello, world!</h1>';
let tag = str.match(/<(.*?)>/);
alert( tag[0] ); // <h1>
alert( tag[1] ); // h1
প্যারেন্টেসিসগুলো নেস্টেডও হতে পারে। এক্ষেত্রেও রেজাল্টে এরা বাম থেকে ডানে আসবে।
উদাহরণস্বরূপ, যখন আমরা এই ধরণের ট্যাগে subject:<span class="my">
অনুসন্ধান করব আমরা ফলাফলটিকে নিম্নোক্তভাবে রাখতে পারব:
১. পুরো কন্টেন্টটি: match:span class="my"
।
২. ট্যাগ নামটি: match:span
।
৩. ট্যাগ অ্যাট্রিবিউট: match:class="my"
।
সুতরাং নেস্টেড প্যাটার্নটি হবে এমন: pattern:<(([a-z]+)\s*([^>]*))>
।
দেখুন এরা কিভাবে ক্রম হয় (প্যারেন্টেসিসের উপর ভিত্তি করে বাম থেকে ডানে):
উদাহরণ:
let str = '<span class="my">';
let regexp = /<(([a-z]+)\s*([^>]*))>/;
let result = str.match(regexp);
alert(result[0]); // <span class="my">
alert(result[1]); // span class="my"
alert(result[2]); // span
alert(result[3]); // class="my"
result
এর শূন্যতম ইনডেক্সে সম্পূর্ণ কন্টেন্টটি।
এরপর গ্রুপিং, ওপেনিং প্যারেন্টেসিসের এর উপর নির্ভর করে বাম থেকে ডানে। প্রথম গ্রুপটি হবে result[1]
। এখানে পুরো ট্যাগ কন্টেন্টটি আসবে।
এরপর result[2]
হল দ্বিতীয় ওপেনিং প্যারেন্টেসিসের pattern:([a-z]+)
কন্টেন্ট - ট্যাগ নাম, এরপর result[3]
ট্যাগ অ্যাট্রিবিউট: pattern:([^>]*)
।
স্ট্রিংয়ের প্রতিটি গ্রুপ:
যদি কোন গ্রুপ অপশনাল হয় এবং কোন মিল না পায় (যেমন এই কোয়ান্টিফায়ারটি pattern:(...)?
), result
অ্যারেতে আইটেমটি undefined
হিসেবে থাকবে।
উদাহরণস্বরূপ, রেগুলার এক্সপ্রেশনটি দেখুন pattern:a(z)?(c)?
। এটি প্রথমে "a"
খুঁজে অতঃপর "z"
এবং "c"
কে অপশনাল হিসেবে খুঁজে।
যদি আমরা একটি ক্যারাক্টার subject:a
এর জন্য প্যাটার্নটি ব্যবহার করি, তাহলে ফলাফলটি হবে:
let match = 'a'.match(/a(z)?(c)?/);
alert( match.length ); // 3
alert( match[0] ); // a (সম্পূর্ন সাবজেক্ট)
alert( match[1] ); // undefined
alert( match[2] ); // undefined
অ্যারেটির সাইজ 3
, কিন্তু গ্রুপ ইনডেক্স গুলো 'undefined'।
এখানে পূর্বেরটির চেয়ে আরেকটি জটিল সাব্জেক্ট আছে subject:ac
:
let match = 'ac'.match(/a(z)?(c)?/)
alert( match.length ); // 3
alert( match[0] ); // ac (সম্পূর্ন সাবজেক্ট)
alert( match[1] ); // undefined, কেননা (z)? এর সাথে কোন মিল নেই
alert( match[2] ); // c
অ্যারেটির সাইজ: 3
। কিন্তু pattern:(z)?
এর জন্য কোন ফলাফল নেই, সুতরাং অ্যারেটি হবে ["ac", undefined, "c"]
।
```warn header="matchAll
হল একটি নতুন মেথড, এজন্য পলিফিলের দরকার হতে পারে"
`matchAll` পুরাতন ব্রাউজারের জন্য কাজ করবে না।
এজন্য পলিফিলের দরকার, যেমন https://github.com/ljharb/String.prototype.matchAll.
যখন আমরা সকল ম্যাচের জন্য ফ্ল্যাগ (`pattern:g`) দ্বারা অনুসন্ধান করব, `match` মেথডটি গ্রুপ কন্টেন্টগুলো রিটার্ন করে না।
যেমন, স্ট্রিংটি হতে সকল ট্যাগগুলো খুঁজি:
```js run
let str = '<h1> <h2>';
let tags = str.match(/<(.*?)>/g);
alert( tags ); // <h1>,<h2>
রেজাল্টে আমরা ম্যাচকৃত সকল অ্যারে দেখি, কিন্তু তাদের প্রত্যেকের গ্রুপ কন্টেন্টের বিস্তারিত নেই। কিন্তু সাধারণত আমাদের ক্যাপচারিং গ্রুপের কন্টেন্ট গুলো রেজাল্টে লাগতে পারে।
এজন্য, আমাদের সার্চিংটা str.matchAll(regexp)
এই মেথডের সাহায্যে চালাতে হবে।
এটি জাভাস্ক্রিপ্টে match
মেথডের অনেক পরে সংযুক্ত হয়েছে, এজন্য এটি "নতুন এবং উন্নত ভার্সন"।
match
এর মত এটিও মিলগুলো খুঁজে, কিন্তু match
এর সাথে ৩টি পার্থক্য আছে:
১. এটি অ্যারে রিটার্নের পরিবর্তে একটি ইটারেবল অবজেক্ট রিটার্ন করে।
২. যখন pattern:g
এই ফ্ল্যাগটি থাকে, এটি প্রতিটি মিলকে গ্রুপ কন্টেন্ট সহ একটি অ্যারে আকারে থাকে।
৩. যদি কোন মিল না থাকে, এটি null
রিটার্নের পরিবর্তে একটি এম্পটি ইটারেবল অবজেক্ট রিটার্ন করে।
উদাহরণস্বরূপ:
let results = '<h1> <h2>'.matchAll(/<(.*?)>/gi);
// results - অ্যারে পরিবর্তে একটি ইটারেবল অবজেক্ট
alert(results); // [object RegExp String Iterator]
alert(results[0]); // undefined (*)
results = Array.from(results); // অ্যারেতে নিয়ে যায়
alert(results[0]); // <h1>,h1 (1st tag)
alert(results[1]); // <h2>,h2 (2nd tag)
আমরা দেখছি, প্রথম পার্থক্যটি অনেক গুরত্বপূর্ণ, (*)
দ্বারা নির্দেশিত লাইনটি খেয়াল করুন। আমরা মিলটিকে results[0]
এর মধ্যে পায় না, কেননা অবজেক্টটি সুডোঅ্যারে নই। আমরা এটিকে Array.from
এর মাধ্যমে Array
তে নিতে পারি। সুডোঅ্যারে এবং ইটারেবল সম্পর্কে বিস্তারিত জানতে পারবেন এই আর্টিকেলে info:iterable।
Array.from
ছাড়াও আমরা লুপের মাধ্যমে রেজাল্ট গুলো দেখতে পারি:
let results = '<h1> <h2>'.matchAll(/<(.*?)>/gi);
for(let result of results) {
alert(result);
// প্রথম অ্যালার্ট: <h1>,h1
// দ্বিতীয়: <h2>,h2
}
...অথবা destructuring ব্যবহারের মাধ্যমে:
let [tag1, tag2] = '<h1> <h2>'.matchAll(/<(.*?)>/gi);
matchAll
দ্বারা রিটার্নকৃত রেজাল্টের প্রতিটি ম্যাচ ফ্ল্যাগ pattern:g
ছাড়া match
মেথডের মত: তবে এর সাথে দুটি অতিরিক্ত প্রোপার্টি থাকে index
(স্ট্রিংয়ে মিলকৃত ইনডেক্সটি) এবং input
(সোর্স স্ট্রিং):
let results = '<h1> <h2>'.matchAll(/<(.*?)>/gi);
let [tag1, tag2] = results;
alert( tag1[0] ); // <h1>
alert( tag1[1] ); // h1
alert( tag1.index ); // 0
alert( tag1.input ); // <h1> <h2>
```smart header="matchAll
এর রেজাল্ট অ্যারে না হয়ে ইটারেবল অবজেক্ট কেন?"
কেন এই মেথডটি এভাবে ডিজাইন করা হয়েছে? এর কারণ সহজ - অপ্টিমাইজেশনের জন্য।
matchAll
কল হলে এটি স্ট্রিংয়ে সার্চ করে না। তার পরিবর্তে, রেজাল্ট ইনিশিয়াল না হয়ে এটি একটি ইটারেবল অবজেক্ট রিটার্ন করে। এবং ইটারেটরের সময় সার্চ সম্পন্ন হয়, যেমন লুপে।
সুতরাং, এটি প্রয়োজনমত রেজাল্ট খুঁজে পায়, এর বেশি না।
যেমন কোন টেক্সটে ১০০ টি ম্যাচ আছে, এবং for..of
এর মাধ্যমে আমরা ৫টি মিল খুঁজি, তারপর আমরা লুপ হতে break
এর মাধ্যমে বের হয়ে যেতে পারি। সুতরাং ইঞ্জিনের বাকী ৯৫টি মিল খুঁজার জন্য অতিরিক্ত সময় অতিবাহিত করা লাগবে না।
## গ্রুপের নামকরণ
ইনডেক্স দিয়ে গ্রুপগুলোকে মনে রাখা কষ্টসাধ্য। সহজ প্যাটার্নগুলোর জন্য এটি সহনীয়, তবে জটিল প্যাটার্নগুলোর জন্য প্যারেন্টেসিস গুনে ইনডেক্সিং করা অসুবিধাজনক। আমাদের কাছে প্যারেন্টেসিসের নামকরণের একটি উপায় আছে।
শুরুর প্যারেন্টেসিসের পর `pattern:?<name>` লিখার মাধ্যমে আমরা নাম দিতে পারি।
যেমন, তারিখকে আমরা এভাবে ফরম্যাট করতে পারি "year-month-day":
```js run
*!*
let dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;
*/!*
let str = "2019-04-30";
let groups = str.match(dateRegexp).groups;
alert(groups.year); // 2019
alert(groups.month); // 04
alert(groups.day); // 30
এখানে দেখতে পাচ্ছি, ক্যাপচারিং গ্রুপগুলো .groups
এর প্রপার্টি হিসেবে আছে।
একাধিক তারিখ বের করার জন্য আমাদের pattern:g
এর সাহায্য নেয়া লাগবে।
গ্রুপের সাথে সম্পূর্ণ মিল খুঁজার জন্য matchAll
মেথডের সাহায্য লাগবে:
let dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/g;
let str = "2019-10-30 2020-01-01";
let results = str.matchAll(dateRegexp);
for(let result of results) {
let {year, month, day} = result.groups;
alert(`${day}.${month}.${year}`);
// first alert: 30.10.2019
// second: 01.01.2020
}
str.replace(regexp, replacement)
মেথডের সাহায্যে regexp
এর সাথে মিলকৃত কন্টেন্ট সমূহকে রিপ্লেস করা যায়, এবং str
এর replacement
এ প্যারেন্টেসিস কন্টেন্টসমূহ ব্যবহার করতে পারি। এটি করা যায় pattern:$n
এর মাধ্যমে, যেখানে pattern:n
হল গ্রুপ নাম্বার।
যেমন,
let str = "John Bull";
let regexp = /(\w+) (\w+)/;
alert( str.replace(regexp, '$2, $1') ); // Bull, John
গ্রুপের নামকরণ এর ক্ষেত্রে ব্যবহার করা যায় এভাবে pattern:$<name>
।
যেমন, চলুন আমাদের তারিখটিকে "year-month-day" থেকে "day.month.year" এভাবে সাজাই:
let regexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/g;
let str = "2019-10-30, 2020-01-01";
alert( str.replace(regexp, '$<day>.$<month>.$<year>') );
// 30.10.2019, 01.01.2020
অনেক সময় আমাদের গ্রুপ ক্যাপচারিং এমনভাবে করা লাগে, যেন ওই গ্রুপের কন্টেন্ট রেজাল্টের মধ্যে না আছে।
একটি গ্রপের কন্টেন্ট কে রেজাল্ট থেকে বাদ দিতে পারি শুরুর প্যারেন্টেসিস পর pattern:?:
দেয়ার মাধ্যমে।
যেমন, আমরা pattern:(go)+
এটিকে খুঁজতে চাই, কিন্তু আমরা (go
) এর কন্টেন্টকে অ্যারের আলাদা আইটেম হিসেবে চাই না, সুতরাং আমরা এভাবে লিখতে পারি: pattern:(?:go)+
সুতরাং এজন্য আমরা শুধু এই নামটি match:John
১ম ইনডেক্সে পাব:
let str = "Gogogo John!";
*!*
// ?: এর দ্বারা 'go' কে ক্যাপচারিং হতে বাদ দিতে পারি
let regexp = /(?:go)+ (\w+)/i;
*/!*
let result = str.match(regexp);
alert( result[0] ); // Gogogo John (full match)
alert( result[1] ); // John
alert( result.length ); // 2 (আর কোন আইটেম নেয়)
প্যারেন্টেসিস গ্রুপগুলো রেগুলার এক্সপ্রেশনের একটি অংশ, সুতরাং কোয়ান্টিফায়ারগুলো এদের সম্পূর্নটার উপর কাজ করবে।
প্যারেন্টেসিস গ্রুপগুলো বাম থেকে ডানে ক্রম করা হয়, এবং চাইলে এদের নামকরণও করা যেতে পারে (?<name>...)
।
কন্টেন্ট যখন গ্রুপদ্বারা ম্যাচ হয়, তখন এদের রেজাল্টে পাওয়া যায়:
str.match
মেথডটি এই ফ্ল্যাগছাড়াpattern:g
ক্যাপচারিং করলে ক্যাপচারিং কন্টেন্টগুলো রিটার্ন করে।str.matchAll
মেথডটি সর্বদা ক্যাপচারিং গ্রুপগুলো রিটার্ন করে।
যদি প্যারেন্টেসিসে নাম না থাকে, তাহলে অ্যারেতে এদের গ্রুপের ক্রমানুসারে পাওয়া যাবে। এবং গ্রুপের নামকরণ করলে এরা groups
এর প্রপার্টি হিসেবে থাকবে।
গ্রুপ কন্টেন্টগুলো আমরা রিপ্লেসম্যান্টের সময় ব্যবহার করতে পারব str.replace
: ইনডেক্স এর ক্ষেত্রে $n
এবং নামের ক্ষেত্রে $<name>
।
আমরা গ্রুপের শুরুতে pattern:?:
ব্যবহারের মাধ্যমে রেজাল্ট হতে এদের বাদ দিতে পারি। আমরা যখন সম্পূর্ন গ্রুপে কোয়ান্টিফায়ার ব্যবহার করব, কিন্তু রেজাল্টে এদের আলাদা করে চায় না তখন এটি ব্যবহার করতে পারি। এছাড়াও স্ট্রিং রিপ্লেসম্যান্টের সময় আমরা প্যারেন্টেসিস ব্যবহার করতে পারব না।