Regex là gì? Ứng dụng Regex để kiểm tra dữ liệu trong Swift

by DaoNM2
601 views

Như mọi người đã biết, tất cả các ứng dụng hiện nay đều có những ô nhập (TextField, TextView …) để hỗ trợ người dùng điền thông tin của họ và gửi về phía server để xử lí. Khi này để giảm tải công việc cho server chúng ta cần phải kiểm tra dữ liệu để loại bỏ bớt các trường hợp dữ liệu không đúng trước khi gửi lên server. Để làm việc này thì thông thường chúng ta sẽ nghĩ ngay đến Regex. Vậy Regex là gì? Áp dụng Regex vào source code Swift như nào? mời các bạn theo dõi tiếp bài viết của mình nhé.

Regex là gì?

Regex là viết tắt của Regular Expression, tiếng việt được gọi là Biểu Thức Chính Quy. Regex là một công cụ rất mạnh trong việc xử lí chuỗi, nó thường được dùng để kiểm tra tính hợp lệ của một chuỗi hay tìm kiếm, vì vậy chúng ta nên trang bị cho mình một số kiến thức cơ bản về Regex để có thể xử lí công việc nhẹ nhàng và nhàn hạ hơn.

Nhược điểm của Regex

Công nhận regex là một công cụ rất mạnh trong việc xử lí chuỗi, tuy nhiên nó cũng có một số nhược điểm như sau:

Khó đọc

Nhìn vào đoạn Regex: ^([A-Za-z0-9!@#$%^&*?]{8,})$

Khi mới bắt đầu chúng ta sẽ rất khó để biết được regex này đang làm nhiệm vụ gì. Vì nó được mô tả bới các kí tự không quen thuộc với ngôn ngữ chúng ta hay dùng.

Dễ quên

Do nó khó đọc, khó hiểu nên nó cũng khiến chúng ta dễ quên, vì vậy mỗi lần cần đến 1 regex nào là chúng ta thường phải tìm đến google để trợ giúp. Để khắc phục nhược điểm này mỗi khi chúng ta làm việc mới một regex khó hoặc dài thì chúng ta sẽ lưu lại vào 1 file lưu trữ, để sau này dùng đến tìm lại cho nhanh.

Regex dùng để làm gì?

Regex thường được dùng để tìm kiếm, để tách chuỗi, để kiểm tra chuỗi, …

1. Dùng để kiểm tra chuỗi

Ví dụ ta cần kiểm tra độ phức tạp của mật khẩu với yêu cầu như sau:

  • Phải có ít nhất một kí tự viết thường
    Regex: [a-z]: Sẽ khớp với bất kì kí tự viết thường nào trong chuỗi(a-z)
    Input: AAA -> FALSE
    Input: aabc
    hoặc Aza -> TRUE
  • Phải có ít nhất một kí tự viết hoa
    Regex: [A-Z] : Sẽ khớp với bất kì kí tự viết hoa nào trong chuỗi(A-Z)
    Input: AAA hoặc Abc -> TRUE
    Input bbca -> FALSE
  • Phải có ít nhất một kí tự số
    Regex: [0-9] : Sẽ khớp với bất kì kí tự số nào trong chuỗi(0-9)
    Input: 1234 hoặc A0bc -> TRUE
    Input bbca -> FALSE
  • Phải có ít nhất một trong số kí tự đặc biệt sau @!#$
    Regex: [@!#$] : Sẽ khớp với bất kì kí tự số nào trong chuỗi(@!#$)
    Input: 12@4 hoặc A!bc -> TRUE
    Input bbca -> FALSE
  • Không được có dấu cách
    Regex: [ ] : Sẽ khớp với bất kì kí tự nào trong chuỗi là space
    Input: 12 4 hoặc A bc -> TRUE
    Input bbca -> FALSE

2. Dùng để tách chuỗi

Ví dụ chúng ta có một chuỗi kí tự như sau: ABCxyz123mM

  • Tách lấy hết các kí tự viết hoa từ chuỗi trên ta sử dụng regex: [A-Z], ta sẽ thu được kết quả từ chuỗi ABCxyz123mM -> ABCM
  • Tách lấy hết kí tự viết thường ta dùng regex: [a-z], ta thu được kết quả như sau: ABCxyz123mM -> abcm
  • Tách lấy hết kí tự số ta thường dùng regex: [0-9], Ta thu được kết quả như sau: ABCxyz123mM -> 123

3. Dùng để tìm kiếm

Regex khá là phổ biến, nó có mặt trên hầu hết các IDE hiện nay. xCode, Android studio, Subline text, note pad ++, …

Ví dụ phần mềm Subline text

Để kích hoạt tính năng này các bạn bấm command + f (tổ hợp tìm kiếm) và chọn mục regex (.*)

Ảnh dưới đây mình có viết 1 đoạn regex: ^N.*o$ để tìm ra chuỗi bắt đầu bằng kí tự N và có kết thúc bằng kí tự o

Giải thích ý nghĩa các kí tự để viết Regex

Sau đây mình sẽ giải thích kĩ hơn về các kí tự để viết regex và cách sử dụng chúng.

1.Kí tự thường

Mã RegexMô tảGhi chú
a|bKhớp với a hoặc b
[0-9]Khớp với các kí tự là số 0,1,2..9
[a-z]Khớp với các kí tự viết thường từ a tới zNếu đổi z thành c thì sẽ là từ a tới c
[ABC]Khớp với các kí tự là ABC
[^ABC]Khớp với các kí tự không phải là ABCNếu dấu ^ xuất hiện phía trong [] có nghĩa nó là
Phủ định của tập hợp đó
\dKhớp với số bất kìthay thế cho [0-9]
\DKhớp với các kí tự không phải sốPhủ định của \d
\sKhớp với tất cả kí tự là khoảng trằng, tab, hoặc xuống dòng
\SKhớp với tất cả các kí tự không phải là khoảng trắng, tab hoặc xuống dòngPhủ định của \s
\S+Khớp với một hoặc nhiều kí tự không phải là khoảng trắng, tab hoặc xuống dòng
\wKhớp với bất kì ký tự chữThay thế cho [a-zA-Z0-9]
\WKhớp với kí tự bất kì không phải chữphủ định của \w
\bKhớp khi kí tự trước đó nằm ở cuối chuỗiregex: dao\b
minhdao: true
daominh: false
\BPhủ định của \b

2. Kí tự đặc biệt

RegexMô tảGhi chú
.Khớp với tất cả các kí tự trừ kí tự xuống dòngThay thế cho [^\n\r]
^Bắt đầuregex: ^dao
daominh: True
minhdao: False
$Kết thúcregex: dao$
daominh: False
minhdao: True
|Điều kiện hoặca|b
a: true
b: true
c: false
\Biến 1 kí tự đặc biệt thành kí tự thường hoặc ngược lạid: kí tự d thông thường
\d: khớp với kí tự số bất kì

3. Lặp

RegexMô tảGhi chú
*Xuất hiện 0 hoặc nhiều lầnTương đương {0,}
+Xuất hiện 1 hoặc nhiều lầnTương đương {1,}
?Xuất hiện 0 hoặc 1 lầnTương đương {0,1}
{x,y}Xuất hiện từ x lần tới y lần{3}: xuất hiện đúng 3 lần
{3,}: xuất hiện từ 3 lần hoặc nhiều hơn
{3, 10} Xuất hiện từ 3 lần đến 10 lần

4. Nhóm

RegexMô tảGhi chú
()Nhóm nhiều mã lại với nhau tạo thành nhóm điều kiện
(?:x)Khớp với x nhưng không nhớ kết quả khớp
x(?=y)Chỉ khớp x nếu ngay sau x là y
x(?!y)chỉ khớp x nếu ngay sau x không phải y

Ứng dụng regex vào việc kiểm tra dữ liệu UITextField bằng ngôn ngữ swift

Để ứng dụng được regex vào việc kiểm tra dữ liệu ở swift chúng ta cần biết một chút kiến thức cơ bản về regex(mình đã giới thiệu ở phía trên) và hiểu rõ về cách hoạt động của TextField

Các hàm common cần dùng

extension String {
    
    /// check string is match regex or not
    /// - Parameter regex: regular expression
    /// - Returns: true if match
    func isMatches(_ regex: String) -> Bool {
        do {
            let regex = try NSRegularExpression(pattern: regex)

            let matches = regex.matches(in: self, range: NSRange(location: 0, length: self.count))
            return !matches.isEmpty
        } catch {
            print("Something went wrong! Error: \(error.localizedDescription)")
        }

        return false
    }
    
    /// Return string matchs regex
    /// - Parameter regex: regular expression
    /// - Returns: all string match regex
    func filter(regex: String) -> String {
        
        do {
            let regex = try NSRegularExpression(pattern: regex)

            let results = regex.matches(in: self, range: NSRange(self.startIndex..., in: self))
            
            return results.map { String(self[Range($0.range, in: self)!]) }.joined(separator: "")
        } catch {
            print("Something went wrong! Error: \(error.localizedDescription)")
        }
        
        return ""
    }
    
    /// Remove mark in string
    var folded: String {
        self.folding(options: .diacriticInsensitive, locale: nil)
            .replacingOccurrences(of: "đ", with: "d")
            .replacingOccurrences(of: "Đ", with: "D")
    }
}
  • func isMatches(_ regex: String) -> Bool: hàm này dùng để kiểm tra xem chuỗi có khớp với regex hay không?
  • func filter(regex: String) -> String: Hàm này dùng để lấy ra tất cả các kí tự thoả mãn điều kiện của regex
  • var folded: String : giúp biến đổi các kí tự có dấu về không dấu VD: Đ -> D, ê -> e …
extension UITextField {
    
    /// validate input data
    /// - Parameters:
    ///   - maxLength: max char of text field
    ///   - range: location and lenth of current selected text
    ///   - string: new string will be replacce at range
    func validateInput(maxLength: Int, range: NSRange, string: String) {
        guard let textFieldText = self.text else {
            return
        }
        
        if self.text.safeValue.count == maxLength, !string.isEmpty {
            return
        }
        
        let text: NSString = textFieldText as NSString
        let finalString: String = text.replacingCharacters(in: range, with: string)
        let newString = String(finalString.prefix(min(maxLength, finalString.count)))
        
        self.text = newString
        let countChange: Int = newString.count - textFieldText.count
        let validateCount: Int = countChange > 0 ? countChange : 0
        self.setCursorPosition(range.location + validateCount)
    }
    
    /// Set Cursor position
    /// - Parameter cursorPosition: Int
    func setCursorPosition(_ cursorPosition: Int) {
        guard cursorPosition <= (self.text ?? "").count,
              let posCursor = self.position(from: beginningOfDocument, offset: cursorPosition) else { return }
        DispatchQueue.main.async {
            self.selectedTextRange = self.textRange(from: posCursor, to: posCursor)
        }
    }
}
  • func validateInput(maxLength: Int, range: NSRange, string: String): Dùng khi cần tự validate các kí tự cho phép, hoặc chặn, hoặc copy paste chuỗi, rồi set lại giá trị cho text field.
  • func setCursorPosition(_ cursorPosition: Int): Dùng để di chuyển con trỏ khi copy paste.

Hiểu rõ các func trong UITextField delegate hoạt động

Ở đây mình chỉ nói đến các delegate hay dùng làm nhiệm vụ kiểm tra dữ liệu

  1. func textFieldDidEndEditing(_ textField: UITextField): Dùng để kiểm tra dữ liệu khi người dùng focus out, thường được dùng khi muốn kiểm tra dữ liệu tại thời điểm người dùng kết thúc thao tác nhập dữ liệu.
  2. func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool : thường được dùng khi muốn kiểm tra dữ liệu tại thời điểm người dùng nhập(realtime), dùng để chặn những kí tự không cho phép.
    – textField: đây là text field đang được người dùng tương tác
    – range: Vị trí và độ dài mã text sắp được thay đổi
    – chuỗi được thay thế tại range
    NOTE: Khi replacement string = empty có nghĩa là người dùng đang thực hiện thao tác xoá hoặc dùng gợi ý nội dung hoặc keychain của bàn phím.
  3. event Editting Changed: Dùng khi không chặn kí tự ở hàm 2(shouldChangeCharacterIn), event này chạy khi có thay đổi kí tự trong text field, với điều kiện shouldCHangeCharacter phải return true

Sử dụng regex để kiểm tra và lọc realtime kí tự nhập vào UITextField

Từ các common code phía trên để kiểm tra chuỗi mà người dùng nhập vào chúng ta sẽ thực hiện nó trong hàm shouldChangeCharacter in range

Ví dụ: Text field chỉ cho nhập các kí tự chữ hoa chữ thường và không cho nhập tiếng việt

Ta sẽ làm như sau:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        // case clear/use suggestion content type/ return/
        if string.count == 0 {
            return true
        }
        
        textField.validateInput(maxLength: 20, range: range, string: string.filterWithRegex(regex: "[a-zA-Z]"))
        validatePassword(text: textField.text.safeValue)
        return false
    }

Nếu muốn khi copy paste một chuỗi có dấu tự convert về chuỗi không dấu ta làm như sau:
Sử dụng folded để convert chuỗi về chuỗi không dấu trước khi filter nó với regex

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        // case clear/use suggestion content type/ return/
        if string.count == 0 {
            return true
        }
        
        textField.validateInput(maxLength: 20, range: range, string: string.folded.filterWithRegex(regex: "[a-zA-Z]"))
        validatePassword(text: textField.text.safeValue)
        return false
    }

Tương tự với từng yêu cầu chúng ta sẽ tạo ra các regex khác nhau và đưa vào sử dụng một cách dễ dàng và hiệu quả

Sử dụng Regex để kiểm tra khi người dùng kết thúc nhập liệu

Nếu bạn muốn kiểm tra chuỗi người dùng vừa xong có đúng định dạng email hay không thì ta sử dụng hàm textFieldDidEndEditing khi người dùng kết thúc nhập để kiểm tra như sau:

    func textFieldDidEndEditing(_ textField: UITextField) {
        guard let textFieldText = textField.text else {
            return
        }
        // check email format
        if textFieldText.isMatches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$") {
            // hide error
        } else {
            //show error
        }
    }

Để tránh sai sót khi sử dụng các regex ở nhiều chỗ khác nhau trong project, chúng ta nên tạo ra enum để quản lí các chuỗi regex này như sau:

enum Regex: String {
    case none = "[\\s\\S]"
    case min8CharNoSpace = "^([A-Za-z0-9!@#$%^&*?]{8,})$"
    case haveCharAndNumber = "(?=.*[a-zA-Z])(?=.*[0-9])"
    case haveUpercaseAndLowerCase = "(?=.*[a-z])(?=.*[A-Z])"
    case haveSpecialChar = "[!@#$%^&*?]"
    case vietnamese = "^[a-zA-Z0-9ÀÁÂÃÈÉÊÌÍÒÓÔÕÙÚĂĐĨŨƠàáâãèéêìíòóôõùúăđĩũơƯĂẠẢẤẦẨẪẬẮẰẲẴẶẸẺẼỀỀỂẾưăạảấầẩẫậắằẳẵặẹẻẽềềểếỄỆỈỊỌỎỐỒỔỖỘỚỜỞỠỢỤỦỨỪễệỉịọỏốồổỗộớờởỡợụủứừỬỮỰỲỴÝỶỸửữựỳỵỷỹ\\s]+$"
    case email = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$"
    case emailInput = "[a-zA-Z0-9.@]"
    case password = "[a-zA-Z0-9!@#$%^&*?]"
    
    var maxLength: Int {
        switch self {
        case .none:
            return .max
        case .min8CharNoSpace:
            return 100
        case .haveCharAndNumber:
            return 100
        case .haveUpercaseAndLowerCase:
            return 100
        case .haveSpecialChar:
            return 100
        case .vietnamese:
            return 100
        case .email:
            return 100
        case .emailInput:
            return 100
        case .password:
            return 30
        }
    }
}

Mình hi vọng bài viết này giúp mọi người hiểu rõ hơn về regex và cách ứng dụng nó vào trong công việc. Chúc các bạn thành công!

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.

You may also like