Swift Regex Builder Kullanımı

Hasan Oztunc
Paycell Tech Team
Published in
5 min readSep 4, 2024
Photo by Mika Baumeister on Unsplash

String manipülasyonu ve validasyonu için sıkça kullanılan yöntemlerden biri de Regular Expression’lardır. Regular Expression’lar, kullanışlı ve etkili olmalarına rağmen, anlaması ve kullanması zor olabilen bir yapıya sahiptir.

Regex’ler, Xcode, Visual Studio Code gibi editörlerdeki arama fonksiyonlarından farklı bir şekilde çalışır. Örneğin, Xcode’da bir arama yaparken bir harf veya kelime yazarak ilgili sonuçları bekleriz. Ancak, Regular Expression’lar bu şekilde çalışmaz. Regex’lerle, bir string içinde belirli bir pattern’e göre arama yaparız.

Örneğin, \b(?<!id)\d+şeklindeki bir regex, id ile başlamayan sayıları filtreleyebilir.

Swift içerisinde Regular Expression kullanımı için NSRegularExpression ve NSPredicate sınıflarını kullanabiliyorduk. Ancak, Swift 5.7 ve iOS 16 ile birlikte Apple, Regular Expression’ları daha verimli, açık ve kolay kullanabileceğimiz yeni bir API tanıttı. Swift 5.7 ile gelen bu DSL (Domain Specific Language), önceki \\w+ gibi kullanımları desteklemenin yanı sıra, birazdan bahsedeceğim deklaratif yapısıyla daha anlaşılır ve kullanıcı dostu bir deneyim sunuyor.

Regex Tanımlama

Yeni tanıtılan API’la birlikte artık 3 farklı şekilde Regex tanımı yapabiliyoruz. Bu tanımlama yöntemleri şu şekilde;

  1. Literal olarak.
let digitsRegex = /\d+/

2. String içerisinde

let digitsRegex = #"\d+"#

3. Yeni gelen Regex Builder ile

let digitsRegex = OneOrMore {
CharacterClass.digit
}

İlk iki yöntem, standart regular expression syntax’ını kullanırken, üçüncü yöntem ise Apple’ın yeni Regex Builder’ını içeriyor. Örnek koda baktığımızda, her üç yöntemle de aynı işlevi gören ve bir veya daha fazla rakamı kontrol eden bir regex yazdığımızı görüyoruz. Ancak, yeni Regex Builder, normal regex syntax’ına göre anlaması ve geliştirmesi daha kolay bir yapıya sahip.

Yeni ve Eskinin Beraber Kullanımı

3. yöntem yani yeni tanıtılan Regex Builder’ı diğer yöntemler ile birliktede kullanabiliyoruz. Yeni Regex Builder’ın böyle bir esnekliğide var. Mesela

let regex = Regex(/\d+/)

bu şekilde Regex builder ile literal tanımını beraber kullanabiliyoruz veya

let regex = try Regex(#"/\d+/"#)

bu şekilde Regex builder içinde normal regex syntaxını kullanabiliyoruz.

Bir örnek ile yeni Regex builder’ı inceleyelim…

let testString = """
firstName lastName email phoneNumber
John Doe john@doe.com 0123456789
Jane Doe jane@doe.com 9876543210
James Bond james.bond@mi6.co.uk 0612345678
"""

Bu şekilde bir listemiz olduğunu düşünelim ve bu listeyi alanlarına parçalayıp sonrasında gerekli verilere ulaşalım. Bu işi string operasyonları ile yapmak bir hayli karışık ve zor olacaktır. Regexler ve Swift’in yeni Regex Builder API’si bu iş için biçilmiş kaftan.

Bu işi nasıl yapacağımızı adım adım açıklayalım ve sonrasında adımların her birini koda dökelim.

  1. Listemizi oluşturan stringde fark ettiyseniz sütunlar arasında boşluklar var. Listemizin sütunlarına erişebilmek için öncelikle sütunlar arasındaki boşlukları tespit edecek bir regexe ihtiyacımız var.
  2. Sonrasında her bir sütun’u tespit etmek için ayrı ayrı regexlere ihtiyacımız olacak. Mesela firstName alanını tespit etmek için karakter arayacak veya phoneNumber alanı için numeric karakterlere bakacak regexler oluşturacağız.
  3. Her bir sütunu tespit etmek oluşturduğumuz ufak regexleri birleştirerek bütün listemizi için kullanacağımız bir regex oluşturacağız.
  4. Son olarakta bütün listemiz için kullanacağız regex ile birlikte listemizdeki alanlara erişeceğiz ve yazdıracağız.

1. İlk olarak boşlukların tespiti için kullanacağımız regex’i yazmakla başlayalım.

let fieldSeparator =  Repeat(2...) {
.whitespace
}

Repeat ile regex’te tekrarlı işlemler yapabiliriz. Mesela burada Repeat kullanarak stringmizi içinde en az 2 veya daha fazla whiteSpace olması gerektiğini belirttik.

Boşluklar için regeximizi oluşturduktan sonra veri listemizdeki sütunlar için regexleri yazmaya başlayabiliriz.

2. İsim alanı için regex yazmaya başlayabiliriz.

let nameField = OneOrMore {
NegativeLookahead {
ChoiceOf {
fieldSeparator
CharacterClass.newlineSequence
}
}
CharacterClass.any
}

OneOrMore en az bir kere veya daha fazla için kullanabiliriz. İsim alanı için her türlü karakteri kabul edecek şekilde CharacterClass.any kullandık fakat bu kullanımda her türlü karakteri kabul edeceği için çıktı olarak bize bütün stringi dönecektir. Bunun için NegativeLookahead ile boşlukları ve yeni satır durumlarına kadar kabul et şeklinde bir düzenleme yapıyoruz.

NegativeLookahead belirtilen bir ifade ile takip edilmeyen ifadeleri yakalamakta kullanılır. FirstName alanını yakalamak içinde 2 veya daha fazla boşlukla takip edilmeyen her türlü karakteri yakalamak için kullandık.

let lastNameField = OneOrMore {
NegativeLookahead { fieldSeparator }
CharacterClass.any
}

Soyisim alanı içinde boşluklara kadar her türlü karakteri kabul edecek şekilde isim alanına benzer bir regex oluşturuyoruz.

Soyisim alanının regex’inide hazırladıktan sonra email alanı için regex yazmaya başlayabiliriz

let emailField = Regex {
OneOrMore(.word)
ZeroOrMore(".")
ZeroOrMore(.word)
"@"
OneOrMore(.word)
"."
OneOrMore(.word)
ZeroOrMore(".")
ZeroOrMore(.word)
}

Bu şekilde email alanı için bir regex yazalım. Email regexinde öncekilerden farklı bazı yeni api’lar kullandık. Bu apiları şu şekilde açıklayabiliriz;

String içerisinde yer almayabilecek veya alabilecek gibi 0–1 durumları için ZeroOrMore kullanabiliriz.

Email alanın regex’inide hazırladıktan sonra telefon numarası alanı için kullanacağımız regexi yazalım.

let phoneNumberField = OneOrMore {
CharacterClass.digit
}

CharacterClass.digit kullanarak digit kabul edecek şekilde bir regex yazabiliriz.

3.Her bir sütün için kullanacağımız regexleri parça parça hazırladıktan sonra bu parçaları birleştirerek tek bir Regex haline getirelim.

let recordMatcher = Regex {
nameField
fieldSeparator
lastNameField
fieldSeparator
emailField
fieldSeparator
phoneNumberField
}

4. Bu şekilde Regex builder ile parçaları birleştirdikte sonra listemizi yazdıralım.

let matches = testString.matches(of: recordMatcher)
print("Found \(matches.count) matches")
for match in matches {
print(match.output)
}


Found 3 matches
John Doe john@doe.com 0123456789
Jane Doe jane@doe.com 9876543210
James Bond james.bond@mi6.co.uk 0612345678

Datalarımızı listelemeyi başardık ama ayrıca satırın tamamına tek bir string olarak değilde ayrı ayrı alanları almak isteyebiliriz. Bu durumlar için yeni Regex Builder ile gelen Capture kullanılabilir.

Bunun için parça regexleri toplayıp tek bir regex haline getirdiğimiz recordMatcher regexinde değişiklik yapmamız gerekecek.

let recordMatcher = Regex {
Capture {
nameField
}
fieldSeparator
Capture {
lastNameField
}
fieldSeparator
Capture {
emailField
}
fieldSeparator
TryCapture {
phoneNumberField
} transform: {
Int($0)
}
}

let matches = testString.matches(of: recordMatcher) // 2
print("Found \(matches.count) matches")
for match in matches {
print("Full Row: " + match.output.0)
print("Name: " + match.output.1)
print("LastName: " + match.output.2)
print("Email: " + match.output.3)
print("Phone Number: " + "\(match.output.4)")
}


Found 3 matches
Full Row: John Doe john@doe.com 0123456789
Name: John
LastName: Doe
Email: john@doe.com
Phone Number: 123456789
.
.
.
.

Bu şekilde alanlar için yazdığımız regexleri Capture içine alarak ve sonrasında output.1 , output.2 ile birlikte alanlara erişebiliriz.

Dikkat ettiyseniz telefonda numarası için Capture değilde TryCapture kullandık. TryCapture kullanarak alanlarımızı başka bir type’a cast etme imkanımız var. Telefon numarasını ben Integer olarak kullanmak istediğim için Int’e type cast ettim.

Xcode normal regex syntaxı ile yazılmış regexlerini Regex builder’a convert etmek için bize bir tool sunuyor. Normal regex syntaxı ile yazılmış regexini tarayın ve Mouse Sağ Click->Refactor->Convert To Regex Builder seçin. Xcode regexinizi Regex builder’a convert edecektir. Her zaman iyi sonuçlar vermeyebiliyor ama genel olarak iyi çalıştığını söyleyebilirim.

Sonuç olarak, Regex Builder’ın bu örnekte gösterdiklerimizin ötesinde birçok kullanışlı özelliği bulunuyor. İlk bakışta normal regex’lere göre daha fazla kod yazmak gerekebilir gibi görünse de, yazma ve okuma kolaylığı sağlaması ve normal regex’lerin yapamadığı yetkinlikleri sunması sayesinde, Swift Regex Builder birçok durumda klasik regex’lerden daha avantajlı bir seçenek sunuyor. Umarım keyifle okumuşsunuzdur. 🙂

Referanslar

--

--