Bildiğim kadarıyla, dengeleme grupları .NET'in normal ifade çeşidine özgüdür.
Kenara: Tekrarlanan Gruplar
Öncelikle, .NET'in (yine bildiğim kadarıyla) tek bir yakalama grubunun birden çok yakalamasına erişmenizi sağlayan tek normal ifade çeşidi olduğunu bilmeniz gerekir (geri başvurularda değil, maç tamamlandıktan sonra).
Bunu bir örnekle açıklamak için, kalıbı düşünün
(.)+
ve dize "abcd"
.
diğer tüm normal ifade çeşitlerinde, grup yakalama 1
yalnızca bir sonuç verecektir: d
(not, tam eşleşme elbette abcd
beklendiği gibi olacaktır ). Bunun nedeni, yakalama grubunun her yeni kullanımının önceki yakalamanın üzerine yazmasıdır.
.NET ise hepsini hatırlar. Ve bunu bir yığın halinde yapar. Yukarıdaki normal ifadeyi aşağıdaki gibi eşleştirdikten sonra
Match m = new Regex(@"(.)+").Match("abcd");
onu bulacaksın
m.Groups[1].Captures
Bir mı CaptureCollection
bunun elemanları gelmektedir dört yakalamaları için
0: "a"
1: "b"
2: "c"
3: "d"
burada sayı CaptureCollection
. Yani temelde grup tekrar her kullanıldığında, yığına yeni bir yakalama aktarılıyor.
Adlandırılmış yakalama grupları kullanıyorsak daha ilginç hale geliyor. .NET aynı adın tekrar tekrar kullanılmasına izin verdiğinden, aşağıdaki gibi bir normal ifade yazabiliriz:
(?<word>\w+)\W+(?<word>\w+)
aynı gruba iki kelime dahil etmek için. Yine, belirli bir ada sahip bir grupla her karşılaşıldığında, yığınına bir yakalama itilir. Girişine bu düzenli ifade uygulayarak Yani "foo bar"
ve teftiş
m.Groups["word"].Captures
iki yakalama bulduk
0: "foo"
1: "bar"
Bu, ifadenin farklı bölümlerinden nesneleri tek bir yığına bile itmemizi sağlar. Ancak yine de, bu sadece .NET'in burada listelenen birden fazla yakalamayı izleyebilme özelliğidir CaptureCollection
. Ama bu koleksiyon bir yığın dedim . Bu yüzden olabilir pop ondan şeyler?
Girin: Dengeleme Grupları
Yapabileceğimiz ortaya çıktı. Benzer bir grup kullanırsak , alt ifade eşleşirse (?<-word>...)
son yakalama yığından word
çıkar ...
. Yani önceki ifademizi şu şekilde değiştirirsek
(?<word>\w+)\W+(?<-word>\w+)
Sonra ikinci grup ilk grubun yakalanmasını açacak CaptureCollection
ve sonunda bir boş alacağız . Elbette bu örnek oldukça kullanışsız.
Ancak eksi sözdiziminin bir detayı daha var: Yığın zaten boşsa, grup başarısız olur (alt şablonuna bakılmaksızın). Yuvalama seviyelerini saymak için bu davranışı kullanabiliriz - ve bu, ad dengeleme grubunun geldiği yerdir (ve ilginçleştiği yerdir). Doğru şekilde parantez içine alınmış dizeleri eşleştirmek istediğimizi varsayalım. Her açılış parantezini yığına itiyoruz ve her kapanış parantezi için bir yakalama açıyoruz. Bir kapanış paranteziyle çok fazla karşılaşırsak, boş bir yığın açmaya çalışır ve desenin başarısız olmasına neden olur:
^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*$
Yani tekrarda üç alternatifimiz var. İlk alternatif, parantez olmayan her şeyi tüketir. İkinci alternatif, (
onları yığına iterken s ile eşleşir . Üçüncü alternatif )
, yığından öğeler çıkarırken s ile eşleşir (mümkünse!).
Not: Açıklığa kavuşturmak için, yalnızca eşleşmemiş parantezlerin olmadığını kontrol ediyoruz! Hiç hiçbir parantez içeren dize olduğunu bu araçlar olacak onlar (Maça sizin parantez gereken bazı sözdiziminde) hala sözdizimsel olarak geçerli olduğundan, uyuyor. Eğer parantez en az bir takım sağlamak istiyorsanız, basitçe bir lookahead eklemek (?=.*[(])
sonra sağa ^
.
Yine de bu model mükemmel (veya tamamen doğru) değildir.
Final: Koşullu Kalıplar
Bir yakalama daha var: bu, dizinin sonunda yığının boş olmasını sağlamaz (dolayısıyla (foo(bar)
geçerli olacaktır). .NET (ve diğer birçok çeşidin) burada bize yardımcı olan bir yapı daha var: koşullu modeller. Genel sözdizimi
(?(condition)truePattern|falsePattern)
burada falsePattern
isteğe bağlıdır - atlanırsa yanlış durum her zaman eşleşir. Koşul, bir kalıp veya bir yakalama grubunun adı olabilir. Burada ikinci duruma odaklanacağım. Bir yakalama grubunun adı ise, o zaman truePattern
ancak ve ancak söz konusu grup için yakalama yığını boş değilse kullanılır. Yani, (?(name)yes|no)
"bir name
şeyi eşleştirip yakaladıysa (hala yığında olan), desen yes
kullan , yoksa desen kullan no
" gibi koşullu bir kalıp .
Yani yukarıdaki modelimizin sonuna, -stack boş (?(Open)failPattern)
değilse, tüm desenin başarısız olmasına neden olacak gibi bir şey ekleyebiliriz Open
. Deseni koşulsuz olarak başarısız kılacak en basit şey (?!)
(boş bir negatif önden bakış). Böylece son modelimiz var:
^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*(?(Open)(?!))$
Bu koşullu sözdiziminin kendi başına grupların dengelenmesiyle ilgisi olmadığını, ancak grupların tüm gücünü kullanmak gerektiğini unutmayın.
Buradan gökyüzü sınırdır. Pek çok karmaşık kullanım mümkündür ve değişken uzunluklu bakışlar gibi diğer .NET-Regex özellikleriyle birlikte kullanıldığında bazı sorunlar vardır ( ki bunu kendim zor yoldan öğrenmem gerekiyordu ). Ancak asıl soru her zaman şudur: Bu özellikleri kullanırken kodunuz hala korunabilir mi? Bunu gerçekten iyi bir şekilde belgelemeniz ve üzerinde çalışan herkesin de bu özelliklerin farkında olduğundan emin olmanız gerekir. Aksi takdirde, dizeyi karakter karakter manuel olarak yürüterek ve bir tam sayıdaki yuvalama düzeylerini sayarak daha iyi durumda olabilirsiniz.
Eklenti: (?<A-B>...)
Sözdiziminde ne var ?
Bu bölüm için krediler Kobi'ye gider (daha fazla ayrıntı için aşağıdaki cevabına bakın).
Şimdi yukarıdakilerin tümü ile, bir dizenin doğru şekilde parantez içine alındığını doğrulayabiliriz. Ancak tüm bu parantez içerikleri için gerçekten yakalamalar (iç içe geçmiş) alabilseydik, çok daha yararlı olurdu. Elbette, parantezleri boşaltılmamış ayrı bir yakalama yığınında açıp kapattığımızı ve daha sonra ayrı bir adımda konumlarına göre bazı alt dize çıkarımlarını yaptığımızı hatırlayabiliriz.
Ancak .NET, burada bir başka kolaylık özelliği daha sağlar: Eğer kullanırsak (?<A-B>subPattern)
, yalnızca yığından bir yakalama değil B
, aynı zamanda bu patlamış yakalama B
ile bu mevcut grup arasındaki her şey yığına itilir A
. Öyleyse, parantezleri kapatmak için böyle bir grup kullanırsak ve yığınımızdan iç içe geçme seviyelerini çıkarırken, çiftin içeriğini başka bir yığına da itebiliriz:
^(?:[^()]|(?<Open>[(])|(?<Content-Open>[)]))*(?(Open)(?!))$
Kobi , cevabında bu Canlı Demoyu sağladı
Tüm bunları bir araya getirerek şunları yapabiliriz:
- Keyfi olarak birçok yakalamayı hatırla
- İç içe geçmiş yapıları doğrulayın
- Her yuvalama seviyesini yakalayın
Hepsi tek bir düzenli ifadede. Heyecan verici değilse ...;)
Onları ilk öğrendiğimde faydalı bulduğum bazı kaynaklar: