Türkçe’ye “Düzenli İfâdeler” diye tercüme etmişler. Regular Expressions gibi programcının eli ayağı olan bir âlet için ne tuhaf bir isim. Bâzen Türk programcıların bu saçma tercümeler yüzünden yeni teknolojilere zamanında intibak edemediğini düşünüyorum.
Kısaca Regexp diye geçer bir çok yerde. PCRE (Perl Combatible Regular Expressions) diye görürseniz de şaşırmayın. POSIX târihini, UNIX’teki kullanımını filân es geçersek, bugünkü şöhretini Perl ile yakalayan ve bugün bir çok programlama dilinde rastlayabileceğiniz, son derece kullanışlı bir âlettir. En çok da PHP programcıları kullanır, bunu da söylemeden edemeyeceğim, varsın yine PHP reklâmı yapıyor desinler.
Programcıların işlerinin çoğu, string tipi değişkenlerledir. Rakamlarla olan işler kolaydır; çarparsın bölersin, daha olmadı üssünü alırsın, sonuca ulaşırsın. Ama yazılarla uğraşırken işler öyle kolay olmaz. 500 sayfalık bir katalogda, 5.345,25 TL şeklinde yazılması gereken bütün fiyatlar 5,345.25 TL şeklinde hatalı yazılmış olsa, hepsini birden nasıl düzeltirsiniz? Kısacık bir Regexp deseniyle PHP bu işi sâniyeler içinde yapar.
Desen ya da PatternBu arada şu “desen” kelimesi de İngilizce “pattern” in tercümesi. İşin doğrusu çok da yersiz bir tercüme değil. El işine meraklı hanımlar sağda solda gördükleri kazak, dantel türü şeylerin “desenini” nasıl ilgiyle inceler, ondan “örnek” çıkartırlar bilirsiniz. Deseni, yâni belli bir kurala uygun tekrar eden en ufak parçayı bulduğunuz zaman, işin gerisi kolaydır. Kazak deseninde olduğu gibi…
E-mail, telefon numarası, URL, tarih gibi bilindik bazı şeyleri yakalamak için geliştirilmiş desenleri, Regexp konusunda hiç bilgisi olmayan programcılar da kullanırlar. Üstelik bunu hanımların dantel deseni kopyalamalarından bile kolay yaparlar. Sağdan soldan buldukları hazır desenleri kopyalaya yapıştıra deneyerek, en işlerine yarayan desende karar kılarlar. Dahası, bu tip hazır desenleri bulabileceğiniz, desen kütüphanesi olarak hizmet veren siteler vardır.
Programlamayla uğraşıyorsanız –ki yazıyı buraya kadar okuduysanız muhtemelen öylesiniz– ve şimdiye kadar Regexp’le mesafenizi hep koruduysanız, arada sırada kopyalayıp yapıştırdığınız o desen kodları, muhtemelen size hep Çince gibi gözükmüştür. Telaşa mahal yok, bunu asla anlayışınızın kıtlığına yormayın, Regexp grameri pek okunaklı değildir. Bir çok kişinin kaybettiği nokta da burası olur. Açıldığı sırayla kapatılmamış parantezler, peş peşe saçma sapan karakterler... Çıkacağınız ilk basamak, bir regexp deseninde gördüğünüz her bir karakterin ve dizilişlerinin kesinlikle bir mânâsı olduğunu kabul etmek olacaktır. Birisi o mânâyı çıkartabiliyorsa, pekâlâ siz de yapabilirsiniz. Bu kadar gaz yeterli motivasyonu sağlamadıysa, burada okumayı bırakın. Çünkü bir sonraki paragraftan itibaren, dalışa geçiyorum.
Sınırlayıcı Karakterler ve AyarlarRegexp desenleri iki kısımdan oluşur; birinci kısım desenin kendisidir, ikinci kısımda da nasıl çalışacağı ile ilgili ilâve ayarlar (modifiers) yer alır. Desenin esas gövdesi, sınırlayıcı karakterler (delimiters) arasına yazılır; ayar parametreleri de kapanış sınırlayıcısından sonra hemen bitişiğe yazılır.
/[a-z]+/iMeselâ bu desende sınırlayıcı karakter olarak bölme işâreti “/” kullandım, desenin kendisi “[a-z]+” kısmı ve tahmin ettiğiniz gibi “i” harfi de bir ayar. Devam etmeden önce hemen merakınızı gidereyim: i harfi desenin büyük / küçük harf hassasiyeti olmadan (insensitive) çalışmasını sağlıyor.
Buraya kadarki yazdıklarımdan da anlaşıldığı gibi, sınırlayıcı karakteri seçmek bizim elimizde. Her ne kadar bir çok hazır regexp desende, kural gibi “/” karakterinin kullanıldığını görmek mümkünse de, aslında böyle bir kural yok. Bence programcının ilk ve en önemli ihtiyacı, ufkunun açık olmasıdır. O yüzden bu noktayı aydınlatmayı önemli buluyorum.
Sınırlayıcı karakterinizi seçerken dikkat etmeniz gereken şey, desenin içinde o karakterin geçmiyor olmasıdır. Tabii geçiyorsa dünyanın sonu değil, başına bir “\” işareti koyarak etkisiz hâle getirebilirsiniz (escape). Ama kim zâten yeterince karışık olan deseni, gereksiz ilave karakterlerle daha okunmaz hâle getirmek ister ki? Ben çoğunlukla sınırlayıcı olarak “@” işâretini tercih ediyorum. E-mail adresleriyle ilgili bir desen yazarken kullanışlı olmayacağı kesin, diğer desenlerde ise son derece işe yarıyor.
Perl, Javascript gibi bazı dillerde Regexp desenleri, ayrı bir nesnedir ve aşağıdaki gibi yazılabilir.
desen=/[0-9]+/g;PHP’de ise Regexp desenleri, ilgili fonksiyonlara string olarak verildiğinden ancak aşağıdaki gibi yazılabilir.
$desen='/[a-z]+/i';Bu da pratikte, işin içine bir sınırlayıcı karakter daha girmesi demektir. Yâni deseninizde string sınırlayıcı olarak kullandığınız tek veyâ çift tırnak karakteri geçerse, onları da başına “\” koyarak etkisizleştirmeniz (escape) gerekir.
$desen='/[a-z\']+/i'; //Tabii bunun yerine aşağıdaki tercih edilir. $desen="/[a-z']+/i";String olarak yazıldığında, sınırlayıcı karakterlerin gereksizleştiği bir gerçek. Desen ile ayarları ayıracak bir tek karakter bütün işi hallederdi. Hatta PHP’de ayarlar (modifiers), ilgili fonksiyonlara ayrı parametreler olarak bile verilebilirdi. Ama görünen o ki PHP’yi yazanlar, programcıların geçmişten gelen alışkanlıklarını bozmamak için, sınırlayıcı karakter (delimiter) kavramını korumuşlar.
Regexp Kullanım SeviyeleriRegexp ile üç farklı seviyede işlem yapılabilir.
1. Mantıksal işlem seviyesiDesenimizi, bir metnin (string) üzerinde çalıştırıp, en az bir eşleşme yakalayıp yakalamadığını kontrol etmek istiyorsak, mantıksal işlem seviyesinde bir desen kullanımına ihtiyacımız var demektir. Meselâ elimizdeki bir metnin, gramerine uygun bir e-mail adresi olup olmadığını kontrol edebilmemiz için; desenimizin çalıştırılmasından geriye, doğru (True) veyâ yanlış (False) şeklinde bir boolean değer dönmesi yeterlidir. Bu iş için PHP’de “preg_match” fonksiyonunu kullanmanızı tavsiye ediyorum.
2. Yakalama seviyesiİkinci seviye, karmaşık bir metnin içinden bir takım gerekli bilgileri ayıklamak istediğimizde (parse) kullanılır.
˂img style="border:0px;" src="resim/yolu/dosya_ismi.png" id="resim_id" /˃Meselâ bir HTML kod içinden, img tag’leri ile yer verilmiş resimlerin adreslerini (yukarıdaki örnek için “resim/yolu/dosya_ismi.png”) almak istediğimizde, bu seviyede bir işlem yapacağız demektir.
PHP’de bu işi gören tavsiye edeceğim iki fonksiyon var; birisi “preg_match”, diğeri “preg_match_all”. İsimlerinden de anlaşıldığı gibi, ilk fonksiyon metinden bir tek eşleşme yakalamaya, ikincisi ise desene uyan bütün eşleşmeleri yakalamaya yarıyor.
Bâzen metinden ayıklamak istediğimiz kısım, zâten bir kere geçmesi gereken bir bilgidir, daha fazla geçse bile bizi ilgilendirmiyordur. Bâzen de yazdığımız desen, kendi tabiati itibariyle sâdece bir sonuç döndürebilecek kaabiliyettedir. Bu gibi durumlarda “preg_match” kullanmak; hem kullanım pratikliği, hem de performans açısından fayda sağlar. Diğer bütün durumlar için, behemehal “preg_match_all” kullanırız.
3. Değiştirme seviyesiÜçüncü seviye, her ne kadar en gelişmiş kullanım seviyesi olsa da, anlatması en kolay olanı. Bir metnin içinde geçen ve bizim desenimize uyan bütün eşleşmeleri, istediğimiz başka bir şeyle değiştiriyoruz. Tabii burada alt-desenler (sub patterns), yâni desenin iç parçacıkları önem kazanıyor.
Yazının başındaki örneğe dönersek; “5,345.25 TL” şeklindeki eşleşmeleri “5.345,25 TL” şeklinde düzeltmek için, yakaladığımız her eşleşmenin bazı kısımlarını değiştirmemiz, bazı kısımlarını da olduğu gibi kullanmamız gerekiyor. Bunun için “alt desenler” kullanmamız gerekiyor ki, ileride geniş anlatacağım. PHP’de bu seviyede işlemler için, “preg_replace” fonksiyonunu tavsiye ediyorum.
Desen GövdesiGelelim işin özüne, yani desenin gövdesini yazmaya. Bunun yolu da öncelikle, normal karakterler ve özel karakterleri ayırabilmekten geçer. Normal karakterler, yazıldığı yerde sadece kendisini temsil eden karakterlere denir. Tahmin edebileceğiniz gibi özel karakterler de, yazıldıkları yere göre özel anlamlar taşıyan karakterlerdir. Meselâ bir “a” harfi, metindeki bütün “a” harfleriyle eşleşirken, bir “*” işâreti, özel durumlar haricinde, metindeki “*” işâretleriyle eşleşmiyor.
Özel karakterleri ayırmaktaki zor taraf, çok fazla sayıda olmaları değil. Regexp desenleriyle bir müddet çalıştıktan sonra, hepsine âşinâlık kazanırsınız. Fakat bu karakterlerin bulundukları yere göre anlamlarının değişmesi, hatta bazı işâretlerin; yerine göre özel bir karakter olup, yerine göre normal karaktere dönüşmeleri işi biraz zorlaştırıyor. Bu anlamları yakalayabilmek için, bir Regexp desenini okurken gözlerinizi dört açmanız gerekiyor.
Özel karakterleri normal karakterlere dönüştürmeyi bâzen biz isteriz. Metindeki “*” karakterlerini yakalamak istediğimizde, bu karakterin özel anlamını iptâl etmemiz gerekir. Tıpkı sınırlayıcı karakterlerde olduğu gibi, bu durumda da ilgili karakterin başına gelecek bir iptal (escape) karakteri “\” derdimize derman olur.
1. KarakterlerÖncelikle bütün istisnaları gözardı ederek, özel durumlar haricinde normal ve özel karaktelerin tasnifini yapalım. Özel durumlar haricinde, “[\^$.|?*+()” karakterleri dışında kalan bütün karakterler, kendilerini temsil ederler. Yâni, bir “s” harfi, metindeki bütün “küçük s” harfleriyle eşleşir. Normal karakterlerden oluşan bir deseni, bir metne uyguladığımızda; yalnızca birebir (exact) eşleşmeleri yakalayabiliriz. Meselâ “sakla” desenindeki “k” harfi, kendi başına metindeki bütün “k” harfleriyle eşleşmez. Bir eşleşme olması için, yanındaki harflerin hepsiyle birlikte ve desende yazıldığı sırayla metinde yer alması gerekir. Yâni, “sakla” deseni, sâdece metindeki “sakla” larla eşleşir.
Desen: @sakla@ Metin: Bu yoğurdu sarımsaklasak da mı saklasak, sarımsaklamasak da mı saklasak? “[” karakteriBir “karakter sınıfı” başlatma anlamı taşıdığı için, özel bir karakterdir. Bir karakter sınıfının başlaması, özel karakterlerde değişikliğe sebep olduğu için, ayrı bir başlık hâlinde aşağıda anlatacağım.
“\” karakteriBuraya kadar çok defa atıfta bulunduğumuz bu karakter, özel anlam taşıyan bütün karakterlerin özel anlamlarını iptal etmeye (escape) yarar. Kendi özel anlamını dahî iptal edebilir.
Desen: @c:\\@ Metin: Dosya yolu: c:\program files\php\ext\ “^” karakteriTek başına hiç bir karakteri yakalamaz. “Metnin başlangıcı” anlamını taşır.
Desen: @^Türk@ Metin: Türkçe bilmek için Türk olmaya gerek var mı? “$” karakteri“^” karakterine benzer. Farkı, başlangıç değil “bitiş” anlamı taşımasıdır.
Desen: @\?$@ Metin: Kim? Kiminle? Nerede? Ne zaman? Ne yaptı? Kim gördü? Ne dedi? “.” karakteriSatır sonu karakterleri “\n” ve “\r” hâriç bütün karakterleri yakalar. Kısaca, “herhangi bir karakter” demektir.
Desen: @s.n@ Metin: Hey gidi küheylan, koşmana bak sen! Çatlarsan, doğuran kısrak utansın! “|” karakteriC tabanlı programlama dillerinin hepsinde olduğu gibi “veyâ” anlamı taşır. Kullanım şeklini “alternatifler” bölümününde anlatacağım.
“?”, “*” ve “+” karakterleriTekrar kontrolünde kullanılır. Ne işe yaradıklarını “tekrar kontrolü” bölümünde anlatacağım.
“(” ve “)” karakterleriAlt desen (sub pattern) oluşturmada ve ihtiyâç hâlinde guruplamada kullanılır. Guruplama maksatlı kullanıldığında da yine alt desen oluştururlar. Tafsilâtını “alt desenler” konusunda anlatacağım.
2. Karakter sınıflarıKarakter sınıfları, bir tek karakteri çok alternatifli olarak tanımlamakta kullanılır. “|” işâreti ile verilen “veyâ” anlamını, bir tek karakteri alternatiflendirirken, “|” işâreti kullanmadan verebilmeyi sağlar. Bir karakter sınıfını yazmaya köşeli parantez açma “[” karakteriyle başlar, köşeli parantez kapama “]” karakteriyle bitiririz. Bu ikili arasında, özel karakterler, dışarıdakilerden farklılık gösterir. “^-]\” karakterlerinin özel anlamları varken, diğer bütün karakterler ise kendilerini temsil eder. Yâni bir karakter sınıfı içine yazacağınız bir parantez “(“ işâreti, “alt desen” oluşturmaya yaramaz ve köşeli parantez “[” işâreti, yeni bir karakter sınıfı başlatma anlamı taşımaz.
Desen: @[[)]@ Metin: [\^$.|?*+()Karakter sınıflarıyla ilgili en çok unutulan husus şudur: Uzun uzun yazdığımız bir karakter sınıfı, her zaman için “bir tek karakteri” temsil eder. İçine yazdığımız normal karakterlerin dizilişi, tamâmen önemsizdir. Çünkü birbirlerine alternatif oluştururlar ve onlardan herhangi birisi ile eşleşme sağlanır. Aşağıdaki örnekte, 16 değil 21 eşleşme olduğuna dikkat edin.
Desen: @[tabut]@ Metin: Tahtadan yapılmış bir uzun kutu; baş tarafı geniş, ayak ucu dar. Desen: @[abtu]@ Metin: Tahtadan yapılmış bir uzun kutu; baş tarafı geniş, ayak ucu dar.Bir karakter sınıfının, desen içindeki yeri ise önemini korumaktadır. Desen içinde herhangi bir “tek karakter” nasıl yanındakilerle birlikte ve yazıldığı sırayla eşleşme anlamı taşıyorsa, bir karakter sınıfı bütünü de aynı şekilde değerlendirilir. Yâni “[ld][ıi]” şeklindeki bir desen, “l” veyâ “d” harfi ve tâkip eden “ı” veyâ “i” harfini bir arada bulursa, eşleşme sağlanır. Bu da ayrı ayrı yazıldığında “lı”, “li”, “dı”, “di” alternatiflerinin hepsi demektir.
Desen: @[ld][ıi]@ Metin: Önde yalın kılıç Türkmen Başbuğu, ardında Oğuz'un ellibin tuğu! “^” karakteriSâdece karakter sınıfının en başında yazıldığında özel anlamı vardır, ikinci ve sonraki sıralarda yazılırsa; özel karakter olmaktan çıkıp, kendisini temsil eder. Özel anlamı ise, mantıktaki “değil” (NOT) ifâdesine denk düşmektedir. Yâni, bir karakter seti bu karakterle başlarsa, içinde tanımlanan karakterler hâriç, bütün karakterlerle eşleşir.
Desen: @[^s ]@ Metin: Alan sensin, veren sen, kılan sen. Ne verdinse odur, dahî nemiz var?Yukarıdaki örnekte “s harfi ve boşluk karakteri” hâriç bütün karakterlerle (52 adet) eşleşme sağlanmıştır.
“-” karakteriAralık belirterek birden çok karakteri kısaca tanımlamak için kullanılır. “a-z”, “0-9” şeklindeki kısaltmalar, programcıyı aradaki bütün harf ve rakamları tek tek yazmaktan kurtarır. Bununla birlikte, “a-z” ve “A-Z” şeklindeki kısaltmalar, Türkçe karakterleri ihtivâ etmez. “-” karakteri, karakter setinin başlangıcında yazılırsa, özel anlamını kaybederek, kendisini temsil eder.
Desen: @[-a-l]@ Metin: Kelâm-ı kibâr, kibâr-ı kelâmestGörüldüğü gibi yukarıdaki desen, “a-l” arası Latin-1 karakterler ve “-” karakteri ile eşleşme sağlamıştır.
“]” karakteriKarakter setini bitiren karakter olduğundan, karakter seti içinde yalın hâliyle yer alamaz. Yer alması gerektiğinde, başına bir iptal (escape) karakteri “\” konulur.
“\” karakteriÖzel anlamları iptal eden bu karakter, karakter seti dışında olduğu gibi içinde de özel karakter vasfını hâiz olduğundan, kendisine işâret edilmesi gerektiğinde yan yana iki tâne “\\” yazılır.
3. KısaltmalarSık kullanılan bazı karakter sınıflarının, desen okunurluğunu artırmak ve yazmayı hızlandırmak için kısa yazılışları vardır.
“\d” kısaltması“[0-9]” şeklinde uzun yazılabilecek olan karakter sınıfının kısaltılmışı. Sâdece nümerik karakterlerle eşleşir. Karakter sınıflarında olduğu gibi bunun da “bir tek karakter” ile eşleşeceğini unutmayın.
“\s” kısaltması“[\n\r\t ]” karakter sınıfının kısaltılmışı. Satır sonu, başı, tab ve boşluk karakterlerinin herhangi birisi (whitespace) demektir.
“\D”, “\W” ve “\S” kısaltmalarıKüçük harfli olanların tam tersini temsil ederler, sırayla “[^\d]”, “[^\w]” ve “[^\s]” karakter sınıflarının kısaltılmışıdırlar.
4. AlternatiflerBir desenin hepsinin veyâ bir kısmının, birden çok alternatifin herhangi birisiyle eşleşme sağlamasını istediğimizde, desenimizin ilgili kısmını “|” işaretini kullanarak alternatifli yazabiliriz.
Desen: @er|ım|di@ Metin: Hastayım derdime verem diyorlar. Maraşlı Şeyhoğlu Satılmış'ım ben.Eğer desenin tamâmı alternatifli olmayacaksa, alternatifli kısmı parantez işâretleri “(“, “)” yardımıyla guruplandırırız.
Desen: @d(ın|üşt|iğ)@ Metin: Değil yalnız ardına kimlerin düştüğünü, kimlerin rüyâsına girdiğini bilirim.Alternatif gurubu oluşturan parantezler, özel karakterlerle ilgili bir değişikliğe sebep olmadıklarından, parantez dışında olduğu gibi içinde de karakter sınıfları kullanabiliriz.
Desen: @([01][0-9]|Temmuz|Ağustos|Eylül|Ekim|Kasım|Aralık)@ Metin: 03 – 09 Hazîran civârında başlayıp, 12 – 21 Eylül civârında bitecek.Yukarıdaki örnekte “21” sayısının eşleşmeyi sağlamadığına dikkât edin.
5. Tekrar KontrolüYazdığımız desenin tamâmının veyâ bir kısmının yan yana tekrar adetlerini tanımlamak ve sınırlamak için tekrar kontrolüne ihtiyâcımız vardır. Tek bir karakterin veyâ bir karakter setinin tekrar kontrolünü, hemen bitişine yazacağımız karakterlerle sağlayabiliriz. Birden çok karakterden oluşan bir metnin tekrar kontrolü için ise, o metni parantez “(“, “)” işâretleri arasına alarak guruplamamız gerekir.
“?” karakteriSıfır “0” veyâ bir “1” kez tekrâr etme anlamı taşır. Yâni var olma veyâ yok olma durumlarının ikisini de karşılar.
Desen: @1[0-9]?1@ Metin: Bu desen 11’le olduğu gibi 121’le ve 141’le de eşleşir, 12 ile eşleşmez. Desen: @(p[oa]|ha)sta(hâ)?ne@u Metin: Yanlış: postane, pastane, hastane. Doğru: postahâne, pastahâne, hastahâne. “*” karakteriSıfır “0” veyâ daha çok kez tekrar etme anlamı taşır. Yâni yok olma, var olma ve çok olma durumlarının hepsini birden karşılar. En geniş anlamlı tekrar kontrol karakteridir.
Desen: @˂/?t(able|[hrd])[^˃]*˃@i Metin: ˂Table˃ ˂tr˃ ˂th style=""˃Ad˂/th˃ ˂/tr˃ ˂tr˃ ˂td ˃Feride˂/td˃ ˂/tr˃ ˂/taBle˃Yukarıdaki örnekte yer alan “[^>]” karakter setinin, “>” işâreti hâriç herhangi bir karakter demek olduğunu daha önce öğrenmiştik. Sonuna “*” işâretini koyarak, tag isminden sonra tag’i kapatan “>” işâreti dışında gelebilecek herhangi bir karakterin, eşleşmeyi bozmamasını sağlıyoruz. Desenimizin böyle bir karakter bulunmaması durumunu da karşılaması için, sıfır “0” veyâ daha çok tekrar anlamı taşıyan “*” karakterini özellikle seçiyoruz. Bu örnekte, desenimiz metinde 10 eşleşme ortaya çıkartıyor. Kabaca, HTML tablo tag’lerinin, kurala uygun olan ve olmayan yazılışlarının hepsini yakalamaya yaradığını söyleyebiliriz.
“+” karakteriBir “1” veyâ daha çok kez tekrar etme anlamı taşır.
Desen: @ +@ Metin: Ne ağlarsın benim zülfü siyahım? Bu da gelir bu da geçer ağlama... “{” ve “}” karakterleriTek başına yazıldığında özel anlama sâhip olmayan bu iki karakter, doğru sırayla ve kuralına uygun yazıldığından, en esnek tekrar kontrolü yapısını oluşturur. Üç tür kullanılışı vardır:
1. {n} yazılışı:Süslü parantezler içine, bir tâne sayı yazarsak, tekrar adedini kesin bir sayıya bağlamış oluruz. Yâni ne fazla ne eksik, yalnızca tam “n” adet tekrar ile eşleşme sağlanır.
Desen: @[a-z]{4}@ Metin: İlim ilim bilmektir, ilim kendin bilmektir.Yukarıdaki örnekte 7 eşleşme olduğuna dikkât edin.
2. {n,m} yazılışı:Süslü parantezler içine, virgülle ayrılmış iki tâne sayı yazarsak, tekrar adedinin alt ve üst sınırlarını tespit etmiş oluruz. Yâni en az “n”, en fazla “m” adet tekrar ile eşleşme sağlanır.
Desen: @[a-z]{3,5}@ Metin: Sen kendini bilmezsen, bu nice okumaktır.Yukarıdaki örnekte 5 eşleşme olduğuna dikkât edin. Tekrar kontrolü ifâdeleri, aksi belirtilmedikçe girişkendir: Desene uygun, en uzun eşleşmeyi yakalamaya çalışırlar. Meselâ “bilmezsen” kelimesinden, üçer karakterlik üç eşleşme değil, biri beş diğeri dört karakterlik iki eşleşme çıkar. Bu özelliği tersine çevirmenin yolunu “? karakterinin ikinci özel anlamı” başlığında anlatacağım.
3. {n,} yazılışı:Virgülü koyup, ikinci sayının yerini boş bırakırsak, tekrar adedinin sâdece alt sınırını tespit etmiş, üst sınırını serbest bırakmış oluruz. Yâni “n adet ve daha çok tekrarların hepsi ile” eşleşme sağlanır.
Desen: @[a-z]{5,}@ Metin: Çocukken haftalar bana asırdı; derken saat oldu, derken saniye... “?” karakterinin ikinci özel anlamıBir karakterden, karakter setinden veyâ parantez gurubundan sonra geldiğinde sıfır veyâ bir kez tekrar anlamı taşıyan bu karakter; herhangi bir tekrar kontrol karakterinden sonra yazılırsa, “girişkenliği sınırlayıcı” anlam taşır.
Tekrar kontrol yapıları için “girişkenlik”, aynı ifâdenin eşleşebileceği değişik ihtimâllerden “en uzun olanını yakalaması” demektir. Eğer kontrol karakterinin sonuna bir “?” işâreti konarak girişkenliği sınırlanırsa, bu defâ tersine olarak en kısa olan eşleşmeyi yakalayacaktır.
Desen: @˂a˃.*˂/a˃@ Metin: Daha fazla bilgi için ˂a˃şu˂/a˃ sitedeki ˂a˃bu˂/a˃ sayfaya bakın. Desen: @˂a˃.*?˂/a˃@ Metin: Daha fazla bilgi için ˂a˃şu˂/a˃ sitedeki ˂a˃bu˂/a˃ sayfaya bakın. Ayarlar (Modifiers)Desen gövdesini iyice öğrendikten sonra hâliyle sıra ayarlara geliyor. Bu ayarlar kısmı, programlama dilleri arasında biraz farklılık gösteriyor. Burada yer vereceklerim, PHP’nin Regexp fonksiyonlarında geçerli olanları. Onların da hepsi değil, en mühim olan ve sık kullanılanları. Diğer diller ve diğer ayarlar için, ilgili kaynakları karıştırmanız lâzım.
“i” ayarıDesenin, büyük küçük harf hassâsiyeti olmadan çalışmasını sağlar. Harfin menşei, “Case insensitive” ifâdesindeki “insensitive” kelimesidir.
“m” ayarıBaşlangıç ve bitişi yakalamaya yarayan “^” ve “$” karakterlerinin satır başı ve sonu için de geçerli olmasını sağlar. Desenimizi, içinde satır sonu karakteri “\n” geçmeyen metinler üzerinde uygularsak, hiç etkisi olmaz. Harfin menşei muhtemelen “multiline” kelimesidir.
“s” ayarıBu ayarı kullandığımızda, desenimizdeki “satır sonu hâriç herhangi bir karakter” anlamına gelen “.” işâretleri, satır sonu karakterlerini “\n” de yakalamaya başlar. Harfin menşei “single line” ifâdesidir.
“x” ayarı“Whitespace” denilen boşluk türü (\n\r\t) karakterlerin, karakter setleri içinde yer aldıkları durumlar hâricinde desenden sayılmamasını sağlar. Bu sâyede, deseni program içinde çok satırlı şekilde yazmak mümkün olur.
“e” ayarıpreg_replace fonksiyonu için özel bir ayardır. Fonksiyonun değiştirme işlemlerini bitirdikten sonra, yerine yerleştireceği sonuçları eval’den geçirmesini sağlar. Bu sâyede, yakalanan her bir eşleşme için kendi tanımladığımız bir başka fonksiyonu çağırarak, sonuç üzerinde düzenleme yapma şansımız olur.
“u” ayarıDesenimizin UTF-8 olduğu kabulü ile çalıştırılmasını sağlar. Eğer desenin içinde yer aldığı .php dosyamız UTF-8 karakter kodu ile kayıtlı ise, regexp fonksiyonları kullanırken bu ayarı eklememiz gerekir.
PHP Regexp FonksiyonlarıPHP’de Regexp işlemleri için bir çok fonksiyon var ama burada, yazının önceki kısımlarında tavsiye ettiğim üç tanesini anlatacağım.
1. preg_matchBu fonksiyon esasen beş parametre alır. Ama opsiyonel olan son iki parametresi sıklıkla kullanılmadığından, burada ilk üç parametresi ile anlatacağım. Birinci parametrede desenimizi, ikinci parametrede desenin üzerine tatbik edileceği metni belirtiriz. Üçüncü parametre olarak da, fonksiyonun yakalayacağı eşleşmeleri içine dolduracağı bir değişken göndeririz. Fonksiyonun kendi geri dönüşü ise, eşleşme bulup bulamamasına bağlı olarak “True” veyâ “False” olur.
$metin='˂falan.filan@fisman.com˃ "Falan Filan"'; $desen='/[-w.]+@[-w.]+/u'; if (preg_match($desen, $metin, $yakala)) {print_r($yakala);}Yukarıdaki PHP kodunun ekran çıkışı aşağıdaki gibi olacaktır:
Array ( [0] => falan.filan@fisman.com )Bu fonksiyonun görevi, metindeki sadece ilk eşleşmeyi yakalamak olduğundan, $yakala isimli array türündeki değişkenimizin içinde sadece ilk eşleşmeye ait bilgiler yer alacaktır. Array’in sıfırıncı elemanı, desenin "tamamıyla eşleşen kısmı", sonraki elemanları da, "alt desenlerle eşleşen kısımları" ihtiva eder. Bu örneğimizde alt desenleri kullanmadığımız için, elimize geçen array’de, sadece sıfırıncı eleman olduğunu görüyoruz.
2. preg_match_allSonundaki “all” ifadesinden de anlaşılacağı üzere, bu fonksiyon bir öncekinden farklı olarak, metindeki bütün desen eşleşmelerini yakalar. Aldığı parametreler ve kullanım şekli açısından ise, hiçbir farkı yoktur. Fonksiyonun geriye döndürdüğü bilgi diğerinden farklı olarak “True” veya “False” değil, eşleşme sayısıdır.
$metin='˂falan.filan@fisman.com˃ "Falan Filan", ˂ali@veli.name.tr˃ "Ali Veli"'; $desen='/[-w.]+@[-w.]+/u'; if ($adet=preg_match_all($desen, $metin, $yakala)) { print_r($yakala); echo $adet;}Yukarıdaki PHP kodunun ekran çıkışı aşağıdaki gibi olacaktır:
Array ( [0] => Array ( [0] => falan.filan@fisman.com [1] => ali@veli.name.tr ) ) 2Bu fonksiyonun hazırladığı $yakala değişkeninin diğerinden farkı, desenin tamamı ve alt desenler için alt array’ler ihitva etmesidir. Burada sıfırıncı elemanın sıfırıncı elemanı ($yakala[0][0]), "desenin tamamı için ilk eşleşmeyi" verirken; sıfırıncı elemanın birinci elemanı ($yakala[0][1]), "desenin tamamı için ikinci eşleşmeyi" vermektedir. Bu örnekte alt desen kullanmadığımız için array’in birinci ve daha sonraki elemanları oluşmadı.
3. preg_replaceBu fonksiyon da beş parametre alır ve yine son iki parametresi opsiyonledir. Ama bu fonksiyonun bütün parametrelerinin yerine göre kullanımı olduğundan, hepsini anlatacağım. Diğerlerinden farklı olarak bu fonksiyon, yakaladığı eşleşmeleri istediğimiz bir şeyle değiştirdiğinden, parametre dizilişi farklılık gösterir.
Birinci parametrede desen(ler)imiz, ikinci parametrede eşleşmelerin yerine konacak metin(ler), üçüncü parametrede ise desenin üzerinde çalışacağı metnin kendisi verilir. Dördüncü parametre belirtilirse, kaç eşleşme için değiştirme işlemi yapılacağı sınırlanmış olur. Beşinci parametre olarak bir değişken verilirse, fonksiyon içine kaç değişiklik yaptığını yazar. Fonksiyonun geri dönüşü ise, üzerinde değişiklikler yapılmış sonuç metni olur.
Replace fonksiyonu diğerlerinden çok daha esnek ve kullanışlıdır. Desen, değişiklik ve metin parametrelerinin herbirini, tek bir string veyâ stringlerden oluşan bir array şeklinde alabilir. Eğer desen parametresi tek bir string olursa, değişiklik parametresi de öyle olmalıdır. Ama desen parametresi bir array olup, değişiklik parametresi tek bir string olabilir. Bu durumda fonksiyon, verilen bütün desenlerin eşleşmelerini hep aynı string ile değiştirir.
$metin='İçinde [b]kalın[/b] ve [i]italik[/i] kelimeler bulunan cümle.'; $desen=array('@[b]@ui', '@[/b]@ui', '@[i]@ui', '@[/i]@ui'); $degistir=array('˂b˃', '˂/b˃', '˂i˃', '˂/i˃'); $metin=preg_replace($desen, $degistir, $metin, -1, $adet); echo $metin . ' ' . $adet;Yukarıdaki PHP kodunun ekran çıkışı aşağıdaki gibi olacaktır:
İçinde ˂b˃kalın˂/b˃ ve ˂i˃italik˂/i˃ kelimeler bulunan cümle. 4Ekran çıktısına baktığımızda görüyoruz ki, preg_replace fonksiyonu, metnin üzerinde sırasıyla bütün desenlerin eşleşmelerini aramış ve her bir eşleşmeyi, değiştirme array’inde desenle aynı sırada yer alan string ile değiştirmiş. Dördüncü parametre’yi “-1” verdiğimiz için sınırlama olmaksızın değişiklik yapmıştır.
$metin=array( ' Başında ve sonunda lüzumsuz boşluklar bulunan cümle ', 'İçinde tekrar eden boşluklar bulunan cümle ' ); $desen=array('@^s+@u', '@s+$@u'); $metin=preg_replace($desen, '', $metin); $metin=preg_replace('@s+@u', ' ', $metin); print_r($metin);Yukarıdaki PHP kodunun ekran çıkışı aşağıdaki gibi olacaktır:
Array ( [0] => Başında ve sonunda lüzumsuz boşluklar bulunan cümle [1] => İçinde tekrar eden boşluklar bulunan cümle )Bu örnekte fonksiyonu daha esnek kullandık. Görüldüğü gibi, üzerinde değişiklik yapılacak metni array olarak verdik, bu sebeple fonksiyonun dönüşü de array şeklinde oldu. Ayrıca fonksiyonun ilk kullanımında deseni array, değiştirme kısmını string olarak, ikincisinde ise hem deseni, hem değiştirme kısmını string verdik. İlk çağırışta, baştaki ve sondaki boşlukların hepsini boş stringle değiştiren (yani ortadan kaldıran) fonksiyon; ikinci çağırışta, mükerrer boşlukları bir tek boşluğa indirdi.
Alt DesenlerDesenimizin yakalayacağı eşleşmelerin bir kısmı bizim için daha önemli olduğunda, o kısmı işâretlemek için parantez açma “(” ve kapatma “)” işâretleri yardımıyla alt desenler oluştururuz.
1. Yakalama seviyesindeMesela, bir HTML kodun içinden “a” elementi ile oluşturulmuş bütün linkleri ayıklamak istiyor olalım.
Desen: @˂a[^˃]*href="([^"]*)"[^˃]*˃(.*?)˂/a˃@ui Metin: ˂a href="b.htm"˃b linki˂/a˃ ve ˂a href="c.htm"˃c linki˂/a˃Görüldüğü gibi desenimiz metinde iki eşleşme yakalıyor. Bizim bu eşleşmelerin sadece belli kısımlarına ihtiyacımız olduğundan, o kısımları, alt desenler oluşturarak işaretliyoruz. Bu örnek için alt desenlerimiz, linklerin hedef URL’lerini ve görünür metinlerini elde etmemize yarıyor. Alt desenlerin nasıl çalıştığını anlayabilmek için, PHP’nin regexp fonksiyonları ile bir örnek yapalım:
$metin='˂a href="b.htm"˃b linki˂/a˃ ve ˂a href="c.htm"˃c linki˂/a˃'; $desen='@˂a[^˃]*href="([^"]*)"[^˃]*˃(.*?)˂/a˃@ui'; preg_match_all($desen, $metin, $yakala); print_r($yakala);Yukarıdaki PHP kodunun ekran çıkışı aşağıdaki gibi olacaktır:
Array ( [0] =˃ Array ( [0] =˃ ˂a href="b.htm"˃b linki˂/a˃ [1] =˃ ˂a href="c.htm"˃c linki˂/a˃ ) [1] =˃ Array ( [0] =˃ b.htm [1] =˃ c.htm ) [2] =˃ Array ( [0] =˃ b linki [1] =˃ c linki ) )Ekran çıkışında ise şunu görüyoruz. Elimize geçen array’in sıfırıncı elemanı, desenin tamamıyla eşleşen kısımları, birinci elemanı desenin birinci alt deseniyle eşleşen kısımları, ikinci elemanı da desenin ikinci alt deseniyle eşleşen kısımları ihtiva eden bir liste.
Tam burada dikkat etmemiz gereken iki husus var. Birincisi; alt desenlerin kaçıncı sırada olduğunu, parantezlerin “açılma sıraları” belirler. İkincisi de; alt desen oluşturma maksadı gütmeden kullanılmış parantez gurupları da, alt desen oluştururlar ve sıralamada yer alırlar.
2. Değiştirme seviyesindeDiyelim ki phpBB’de kullanılan BBCode’u html’e çevirmemiz gerekiyor. “[color=red]kelime[/color]” şeklinde yazılmış yerleri “˂span style="color:red"˃kelime˂/span˃” şeklinde düzeltmek için aşağıdaki kodu kullanabiliriz:
$metin='Yazı içinde [color=blue]mavi kelime[/color] '; $metin.='ve [color=red]kırmızı kelime[/color] geçiyor.'; $desen='@\[color=([^\]]+)\](.*?)\[/color\]@ui'; $degistir='˂span style="color:$1"˃$2˂/span˃'; $metin=preg_replace($desen, $degistir, $metin); echo $metin;Yukarıdaki PHP kodunun ekran çıkışı aşağıdaki gibi olacaktır:
Yazı içinde ˂span style="color:blue"˃mavi kelime˂/span˃ ve ˂span style="color:red"˃kırmızı kelime˂/span˃ geçiyor.Eşleştirmeleri değiştirmek için kullandığımız stringde “$1” ve “$2” şeklinde yazılan yerler, sonuçlardan anlaşıldığı gibi, sırasıyla desendeki birinci ve ikinci alt desenlere işaret ediyor.
Desenimizi ufak parçalara ayıralım ve ilk olarak açılış kelimesini yakaladığımız kısmı inceleyelim:
“\[color=([^\]]+)\]”: Bu kısımda öncelikle yakalamak istediğimiz “[color=renk]” türü ifadelerin başındaki ve sonundaki “[“ ve “]” işaretleri, karakter sınıfı tanımlamaya yarayan özel karakterlerle karışacağı için onları “\” işareti ile escape etmemiz gerekiyor. Onları desenden çıkartıp daha ufak parçayla ilgilenelim:
“color=([^\]]+)”: Bu kısımdaki “color=” deseni kolayca anlaşıldığı için onu da çıkartıp inceleyelim:
“([^\]]+)”: Burada baştaki ve sondaki parantez işaretlerinin, “alt desen” oluşturmaya yaradığını biliyoruz. Bütün desen içindeki ilk parantez burada yer aldığı için bu kısmın 1 numaralı alt desen olacağını biliyoruz. Parantez işaretlerini de atıp devam edelim:
“[^\]]+”: Burada görüyoruz ki , bir karakter sınıfı tanımlaması var. Sonuna eklenen + işareti ile karakter sınıfının yakalayacağı karakterlerden peş peşe bir veya daha fazla sayıda gelebileceğini söylemişiz. Karakter sınıfının kendisine bakalım:
“[^\]]”: Öncelikle karakter sınıfı içinde “]” işareti özel işaret olduğu için, onu “\” işareti ile geçersiz kıldığımızı görüyoruz. Daha sonra baştaki “^” işareti ile, “]” işareti hariç bütün karakterleri yakalama anlamı vermişiz. Bu da, “[color=” kısmından sonra, “]” işaretine kadar gelen diğer bütün karakterleri yakalamamıza yarıyor. Yani yakalamak istediğimiz renk kodu, birinci alt desenin içinde olacak.
Sondaki “\[/color\]” kısmında açıklanacak bir şey gözükmüyor. Arada kalan kısmı inceleyelim:
“(.*?)” : Burada parantezlerin ikinci alt desenimizi oluşturmaya yaradığını biliyoruz. “.*” parçası, herhangi bir karakterden, herhangi bir sayıda eşleşmeyi yakalamaya yarıyor. Sonuna eklediğimiz soru işareti ile bu ifadenin girişkenliğini sınırlıyoruz. Böylece “[color=renk]” kısmından sonra gelen ilk “[/color]” ifadesinde, eşleşmenin sağlanmasını sağlıyoruz. Burada soru işaretini kullanmasaydık, PHP kodunun ekran çıkışı aşağıdaki gibi olurdu:
Yazı içinde ˂span style="color:blue"˃mavi kelime[/color] ve [color=red]kırmızı kelime˂/span˃ geçiyor.