Perşembe, Ekim 04, 2007

Regular Expressions Nedir?

Ne zamandır tatildi kitaptı derken programcılıkla ilgili konuları ihmal ettim. Bu yazımda her programcının ihtiyacı olabilecek Regular Expressions (Düzenli İfadeler) konusuna değineceğim. İnternette İngilizce veya Türkçe yazılmış birçok kaynak var ancak ben bu konuyu gerçekten çok zor anladım. Temel maksadım bir web sayfasındaki linkleri çeken bir program yazmaktı ancak işin temel mantığını çok geç kavradığım için bayağı bir uğraştım. Bu yazımda kendi anladığım şekilde çok basitten alarak işin temel mantığını vereceğim sonra işin detayları için güzel birkaç link vereceğim.

Regular Expressions aslında notepad gibi programlarda birşey aramak için kullandığımız mantığın daha kompleks bir şekli. Mesela bir yazı içerisinde "emre" kelimesini aramak için direk "emre" yazıyoruz. Bu zaten normal birşey. Ancak "e" ile başlayıp "e" ile biten 4 harfli herhangi birşey arıyorsak o zaman ifademizi "e..e" olarak veriyoruz. Burada "." işareti herhangi bir karakter anlamına geliyor. Eğer notepad programı regex ifadelerini destekleseydi arama kısmına "e..e" yazdığımızda yazı içerisinde emre, ezme gibi kelimeleri bulacaktı. Aynı şekilde "Cemre" kelimesinin de "emre" kısmını bulabilecekti. Bu noktada hemen bir kod örneği inceleyelim. Örneği C#'tan veriyorum.

public IList ExtractData(string regex, string icerik)
{
IList result = new List();
Match m;
Regex r = new Regex(regex, RegexOptions.IgnoreCase RegexOptions.Compiled);
for (m = r.Match(icerik); m.Success; m = m.NextMatch())
{
result.Add(m.Groups[0].Value);
}
return result;
}


Bu kod içerik içerisinde regex'e uyan bütün sonuçları liste olarak geri döndürecektir. Bir Regex nesnesi oluşturuyoruz ve Regex sınıfının Match metoduna içeriğimizi gönderiyoruz. Metod bize Match tipinde bir nesne döndürüyor. Bu Match nesnesi aramanın başarılı olup olmadığına dair bilgilerle birlikte neyin bulunduğunu da söylüyor. Regex'in gruplama özelliği olduğu için bunu bize Groups adında bir liste olarak veriyor ama grupları şimdilik kullanmayacağımız için biz sıfırıncı elemandan bulunan sonucu öğreniyoruz.

Normalde e ile başlayıp e ile biten kelimeler arayan programlar yazmayız. Güzel bir örnek bir web sayfası kaynağından linkleri ayıklayan bir kod yazmak olabilir. Html kodunda link href='http://www.xxx.com' şeklinde geçer yani amacımız tırnak içinde geçen linki ayıklamak. Bunu yapmak için href='[^']+' şeklinde bir ifade kullanacağız. İsterseniz bu ifadeyi detaylı olarak inceleyelim.

href=' yazarak aradığımız sonucun href=' ile başlaması gerektiğini belirttik. Devamındaki [] parantezi özel bir anlama geliyor. Köşeli parantez harf grupları oluşturmak için kullanılır. İlk örneğimizdeki e..e aramasını e..[ae] şeklinde yazarsak sonu a veya e ile biten herhangi bir harf grubunu seçmiş oluruz. Özetle bize dönecek sonuçlar şu özellikleri içerecek.

e ile başlayacak,
herhangi iki karakterle devam edecek,
sonra a veya e gelecek.

Aslında özel olarak belirtmezsek regex aramayı kelime kelime yapmaz. \b ifadesi boşluk anlamına gelir ve ifademizin başına ve sonuna \b koyarsak tam kelime araması yaparız. Bu durumda ifademizi \be..[ae]\b olarak yazmamız gerekir.

Eğer a,e ile biten kelimeleri değil de a,e ile bitmeyenleri seçmek istersek kümenin başına ^ işareti koyarız. \be..[^ae]\b ifadesi e ile başlayıp a,e den farklı herhangi bir karakterle biten 4 harfli kelimeleri bulur. emar, esin, erez gibi kelimeler başarılı olurken emre esma gibi kelimeler seçim dışında kalır. Küme içindeki ^ işareti kümenin tersi anlamına gelir.

href='[^']+' ifademize geri dönelim. href=' ile başladık ve [^'] ile devam ettik. Sondaki + ve ' işaretini şimdilik görmezden gelerek biraz daha araştırma yapalım. Eğer ifademizi sadece href='[^'] olarak yazarsak o zaman href=' ile başlayan ve sonraki karakteri ' haricinde herhangi birşey olan harf gruplarını seçeriz. Mesela bu ifadeyi

<a href='http://www.emreeren.com/'>Emre'nin sitesi</a> dizisinin içinde çalıştırırsak

<a href='http://www.emreeren.com/'>Emre'nin sitesi</a>

içeriğindeki kalın işaretli kısım döner. Yani sonuç olarak href='h elde ederiz. Aslında istediğimiz linkin tümünü seçmek. Burada da imdadımıza + işareti koşuyor. + işareti tekrarla anlamına gelir. [^']+ yazdığımızda sıradaki karakter ' harici olduğu sürece eşleştirmeye devam edilir. href='[^']+ dediğimizde regex yorumlayıcısı + işaretini görür ve ' işaretini görene kadar karakterleri okumaya devam eder. ' işaretini görünce de durur. Demek ki href='[^']+ ifadesi bize
href='http://www.emreeren.com sonucunu döndürür. Örneğimizin sonundaki ' işareti ise en sonraki ' işaretini de sonuca eklememize yarar yani href='[^']+' yazdığımızda dönecek sonucun sonuna ' işareti de eklenir. Bir daha özetle regex yorumlayıcısının adım adım ne yaptığına bakalım.

Kaynak:
<a href='http://www.emreeren.com/'>Emre'nin sitesi</a>

Regex ifademiz:
href='[^']+'

Yorumlayıcı önce href=' kısmını bulur
<a href='http://www.emreeren.com/'>Emre'nin sitesi</a>

sonra [^'] ifadesini görür ve sıradaki harfin ' işaretinden farklı olup olmadığına bakar. Tırnaktan farklı olduğu için sonuca h harfini de dahil eder.
<a href='http://www.emreeren.com/'>Emre'nin sitesi</a>

sonra + işaretini görür ve bu işlemi ' hariç sıradaki her karakter için devam ettirir ve ' işaretine gelince durur.
<a href='http://www.emreeren.com/'>Emre'nin sitesi</a>

sonra ' ifadesini görür ve sonraki ' işaretini de sonuca ekler.
<a href='http://www.emreeren.com/'>Emre'nin sitesi</a>

Diyelim ki programımızı yazdık bitirdik. Bir de baktık ki başka bir web sayfası kaynağında
<a href="http://www.emreeren.com/"> örneğindeki gibi linklede ' yerine " kullanılmış. Bu durumda örnek kodumuz hiç bir sounç döndürmez çünkü yorumlayıcı ifademize uyan uygun karakter dizisi bulamaz. Bu sayfadan linkleri almak için ifademizi href="[^"]+" şeklinde yazmamız gerekirdi. Tamam ama link kapatmak için ' işareti mi yoksa " işareti mi kullanıldı nereden bileceğiz? Aslında sorumuzun cevabı yine yazımızın içinde var. Karakter kümelemeyi kullanarak iki alternatifi de ifademize ekleyeceğiz.

href=['"][^'"]+['"] (Biraz zor okunuyor ama "' kısmı " ve ' karakterlerinin yan yana yazılmış hali)

İfademizi yukarıdaki şekilde verdiğimizde href= eşlemesinden sonra ", ' karakterlerinden birine uyan, sonra ", ' karakterlerine uymayan bütün (+) karakterleri ve ", ' karakterlerinden birine uyan bir karakteri seçmiş oluyoruz. Bundan sonraki örnekleri basit okunması için kümelenmemiş halde vereceğim.

İşte şimdi can alıcı bir soruya sıra geldi. href='[^']+' ifadesini yazdığımızda linkler

href='http://www.emreeren.com'

şeklinde gelecek. Sonra biz baştaki href=' kısmını ve sonraki ' işaretini kaldırıp linki

http://www.emreeren.com

olarak düzenlemek zorunda kalacağız. Çünkü işimize yarayan bölüm burası. Sonuçların bu şekilde olması için gruplama özelliğini kullanmamız gerekiyor. İfademizde herhangi bir bölgeyi normal parantez içine aldığımızda o kısım bir grup olur. Yani ifademizi href='([^']+)' şeklinde yazarsak baştaki href=' ve sonraki ' işareti parantez dışında kaldığı için onlar grubun dışında kalır. Tabii gruplama kullandığımız için kodumuzda

m.Groups[0].Value yerine m.Groups[1].Value ifadesini kullanmamız gerekir. m.Groups listesinin birinci elemanı her zaman seçimin tümünü içerir. İfademizdeki gruplar da soldan 1,2,... diye numaralanarak listeye eklenir. Yani m.Groups[0].Value yine
href='http://www.emreeren.com' sonucunu döndürecektir ancak parantez içine aldığımız kısım yani http://www.emreeren.com kısmı m.Groups[1].Value sonucu olarak dönecektir.

Şimdi bütün bu öğrendiklerimizle
<a href='http://www.emreeren.com/'>Emre'nin sitesi</a> içinden hem linkin adresini hem de açıklamasını çekelim ve bunları ayrı gruplarda gösterelim. Adım adım ifadeyi yazacağım ve seçimi kalın olarak göstereceğim

href='
<a href='http://www.emreeren.com/'>Emre'nin sitesi</a>

href='[^']+'
<a href='http://www.emreeren.com/'>Emre'nin sitesi</a>

href='[^']+'>
<a href='http://www.emreeren.com/'>Emre'nin sitesi</a>

href='[^']+'[^<]+
<a href='http://www.emreeren.com/'>Emre'nin sitesi</a>

Emre'nin sitesi kısmını da seçimimize dahil ettikten sonra gruplamayı yapıyoruz.

href='([^']+)'([^<]+)

Bu sefer iki parantezimiz yani iki grubumuz var. Bu da demek oluyor ki

m.Groups[0].Value:
href='http://www.emreeren.com'>Emre'nin sitesi

m.Groups[1].Value:
http://www.emreeren.com

m.Groups[2].Value:
Emre'nin sitesi

sonuçlarını içerecektir.

Buraya kadar basit bir uygulamayı detaylı olarak anlatmaya ve olayın temel mantığını vermeye çalıştım. İtiraf etmek gerekirse gerçek hayat uygulamalarında çok daha farklı ve bol alternatif isteyen yapılar karşınıza çıkacak ama regular expressions olası her durum için işe yarayacak daha yığınla özelliğe sahip. Google tabiiki her zaman bir numaralı yardımcınız ama aşağıdaki linkleri de ziyaret edebilirsiniz.

http://www.regular-expressions.info (ingilizce)
...~meren/belgeler/regex/regex.html (Türkçe)

Ayrıca denemelerinizi yapabilmeniz için süper bir online test aracı da http://www.regextester.com/ adresinde.

3 yorum:

Ayşe dedi ki...

Hocam güzel ama biraz eksik olmuş bu kodlar nereye yazılıyor tam olarak o for döngüsünü ne için kullanıyorsun, keşke kullanımı hakkında biraz daha detaylı bilgi verseydin.

Bulent dedi ki...

Hocam 2 gndür internete makale okuyorum en açıklayıcı sizinki olmuş teşekkürederim

Ömer SEYHAN dedi ki...

merhaba,

ben sitemin sql yedeği üzerinde çalışıyorum 2003 yılından beri içerik eklediğim sitemde gereksiz html tagları kullanmışım notepad++ ın regex özelliğini kullanarak vb. tagları silmem gerekiyor. yaptıklarınızı yapınca pek bir ilerleme olmuyor çözemedim nasıl yapabiliriz bu işi