Swift извлечение регулярных выражений соответствует



Я хочу извлечь подстроки из строки, которая соответствует шаблону регулярных выражений.



так что я ищу что-то вроде этого:



func matchesForRegexInText(regex: String!, text: String!) -> [String] {
???
}


вот что у меня есть:



func matchesForRegexInText(regex: String!, text: String!) -> [String] {

var regex = NSRegularExpression(pattern: regex,
options: nil, error: nil)

var results = regex.matchesInString(text,
options: nil, range: NSMakeRange(0, countElements(text)))
as Array<NSTextCheckingResult>

/// ???

return ...
}


проблема в том, что matchesInString выдает мне массив NSTextCheckingResult, где NSTextCheckingResult.range типа NSRange.



NSRange несовместимо с Range<String.Index>, так что это мешает мне использовать text.substringWithRange(...)



любая идея, как достичь этой простой вещи в swift без слишком много строк кода?

611   8  

8 ответов:

даже если matchesInString() метод принимает String в качестве первого аргумента, он работает внутри NSString, и параметр диапазона должен быть задан используя NSString длина, а не как длина строки Swift. В противном случае это будет сбой для "расширенных кластеров графем", таких как"флаги".

по состоянию на Swift 4 (Xcode 9), стандарт Swift библиотека предоставляет функции для преобразования между Range<String.Index> и NSRange.

func matches(for regex: String, in text: String) -> [String] {

    do {
        let regex = try NSRegularExpression(pattern: regex)
        let results = regex.matches(in: text,
                                    range: NSRange(text.startIndex..., in: text))
        return results.map {
            String(text[Range(.range, in: text)!])
        }
    } catch let error {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

пример:

let string = "€4€9"
let matched = matches(for: "[0-9]", in: string)
print(matched)
// ["4", "9"]

Примечание: принудительное разворачивание Range(.range, in: text)! безопасно, потому что элемент NSRange относится к подстроке данной строки text. Однако, если вы хотите избежать этого, то используйте

        return results.flatMap {
            Range(.range, in: text).map { String(text[]) }
        }
.

(более старый ответ для Swift 3 и ранее:)

таким образом, вы должны преобразовать данную строку Swift в NSString а затем извлечь диапазоны. Результат будет автоматически преобразован в массив строк Swift.

(код для Swift 1.2 можно найти в истории редактирования.)

Swift 2 (Xcode 7.3.1):

func matchesForRegexInText(regex: String, text: String) -> [String] {

    do {
        let regex = try NSRegularExpression(pattern: regex, options: [])
        let nsString = text as NSString
        let results = regex.matchesInString(text,
                                            options: [], range: NSMakeRange(0, nsString.length))
        return results.map { nsString.substringWithRange(.range)}
    } catch let error as NSError {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

пример:

let string = "€4€9"
let matches = matchesForRegexInText("[0-9]", text: string)
print(matches)
// ["4", "9"]

Swift 3 (Xcode 8)

func matches(for regex: String, in text: String) -> [String] {

    do {
        let regex = try NSRegularExpression(pattern: regex)
        let nsString = text as NSString
        let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))
        return results.map { nsString.substring(with: .range)}
    } catch let error {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

пример:

let string = "€4€9"
let matched = matches(for: "[0-9]", in: string)
print(matched)
// ["4", "9"]

мой ответ строится поверх заданных ответов, но делает регулярное выражение более надежным, добавляя дополнительную поддержку:

  • возвращает не только совпадения, но и возвращает также все группы захвата для каждого матча (см. примеры ниже)
  • вместо того, чтобы возвращать пустой массив, это решение поддерживает дополнительные матчи
  • избежать do/catch не печатая на консоль и использует guard построить
  • добавляет matchingStrings как ).location != NSNotFound ? nsString.substring(with: result.rangeAt()) : "" } } } } "prefix12 aaa3 prefix45".matchingStrings(regex: "fix([0-9])([0-9])") // Prints: [["fix12", "1", "2"], ["fix45", "4", "5"]] "prefix12".matchingStrings(regex: "(?:prefix)?([0-9]+)") // Prints: [["prefix12", "12"]] "12".matchingStrings(regex: "(?:prefix)?([0-9]+)") // Prints: [["12", "12"]], other answers return an empty array here // Safely accessing the capture of the first match (if any): let number = "prefix12suffix".matchingStrings(regex: "fix([0-9]+)su").first?[1] // Prints: Optional("12")

    Swift 2

    extension String {
        func matchingStrings(regex: String) -> [[String]] {
            guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] }
            let nsString = self as NSString
            let results  = regex.matchesInString(self, options: [], range: NSMakeRange(0, nsString.length))
            return results.map { result in
                (0..<result.numberOfRanges).map { result.rangeAtIndex().location != NSNotFound
                    ? nsString.substringWithRange(result.rangeAtIndex())
                    : ""
                }
            }
        }
    }
    

если вы хотите извлечь подстроки из строки, а не только позицию, (но фактическую строку, включая emojis). Затем, следующее, возможно, более простое решение.

extension String {
  func regex (pattern: String) -> [String] {
    do {
      let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions(rawValue: 0))
      let nsstr = self as NSString
      let all = NSRange(location: 0, length: nsstr.length)
      var matches : [String] = [String]()
      regex.enumerateMatchesInString(self, options: NSMatchingOptions(rawValue: 0), range: all) {
        (result : NSTextCheckingResult?, _, _) in
        if let r = result {
          let result = nsstr.substringWithRange(r.range) as String
          matches.append(result)
        }
      }
      return matches
    } catch {
      return [String]()
    }
  }
} 

Пример Использования:

"someText ️ pig".regex("️")

возвращает следующее:

["️"]

Примечание использование "\w+ " может привести к неожиданному ""

"someText ️ pig".regex("\w+")

вернет этот массив строк

["someText", "️", "pig"]

я обнаружил, что решение принятого ответа, к сожалению, не компилируется на Swift 3 для Linux. Вот модифицированная версия, то, что делает:

import Foundation

func matches(for regex: String, in text: String) -> [String] {
    do {
        let regex = try RegularExpression(pattern: regex, options: [])
        let nsString = NSString(string: text)
        let results = regex.matches(in: text, options: [], range: NSRange(location: 0, length: nsString.length))
        return results.map { nsString.substring(with: .range) }
    } catch let error {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

основные отличия:

  1. Swift на Linux, кажется, требует удаления NS префикс для объектов Foundation, для которых нет эквивалента Swift-native. (См.Swift evolution proposal #86.)

  2. Swift на Linux также требует указание options аргументы для обоих RegularExpression инициализации и matches метод.

  3. по какой-то причине, принуждая a String на NSString не работает в Swift на Linux, но инициализирует новый NSString С String как источник работает.

эта версия также работает с Swift 3 на macOS / Xcode с единственным исключением, что вы должны использовать имя NSRegularExpression вместо RegularExpression.

@p4bloch если вы хотите захватить результаты из серии скобок захвата, то вам нужно использовать rangeAtIndex(index) метод NSTextCheckingResult, вместо range. Вот метод @MartinR для Swift2 сверху, адаптированный для скобок захвата. В массиве, который возвращается, первый результат [0] это весь захват, а затем отдельные группы захвата начинаются с [1]. Я прокомментировал map операция (так легче увидеть, что я изменил) и заменил его вложенным петли.

func matches(for regex: String!, in text: String!) -> [String] {

    do {
        let regex = try NSRegularExpression(pattern: regex, options: [])
        let nsString = text as NSString
        let results = regex.matchesInString(text, options: [], range: NSMakeRange(0, nsString.length))
        var match = [String]()
        for result in results {
            for i in 0..<result.numberOfRanges {
                match.append(nsString.substringWithRange( result.rangeAtIndex(i) ))
            }
        }
        return match
        //return results.map { nsString.substringWithRange( .range )} //rangeAtIndex(0)
    } catch let error as NSError {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

пример использования может быть, скажем, вы хотите разделить строку title year например, "найти Дори 2016" вы могли бы сделать это:

print ( matches(for: "^(.+)\s(\d{4})" , in: "Finding Dory 2016"))
// ["Finding Dory 2016", "Finding Dory", "2016"]

Это очень простое решение, которое возвращает массив строк с матчей

Swift 3.

internal func stringsMatching(regularExpressionPattern: String, options: NSRegularExpression.Options = []) -> [String] {
        guard let regex = try? NSRegularExpression(pattern: regularExpressionPattern, options: options) else {
            return []
        }

        let nsString = self as NSString
        let results = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))

        return results.map {
            nsString.substring(with: .range)
        }
    }

большинство решений выше дают только полное совпадение в результате игнорирования групп захвата, например: ^\d+\s+(\d+)

чтобы получить матчи группы захвата, как ожидалось, вам нужно что-то вроде (Swift4) :

public extension String {
    public func capturedGroups(withRegex pattern: String) -> [String] {
        var results = [String]()

        var regex: NSRegularExpression
        do {
            regex = try NSRegularExpression(pattern: pattern, options: [])
        } catch {
            return results
        }
        let matches = regex.matches(in: self, options: [], range: NSRange(location:0, length: self.count))

        guard let match = matches.first else { return results }

        let lastRangeIndex = match.numberOfRanges - 1
        guard lastRangeIndex >= 1 else { return results }

        for i in 1...lastRangeIndex {
            let capturedGroupIndex = match.range(at: i)
            let matchedString = (self as NSString).substring(with: capturedGroupIndex)
            results.append(matchedString)
        }

        return results
    }
}

вот как я это сделал, я надеюсь, что это приносит новую перспективу, как это работает на Swift.

в этом примере ниже я получу любую строку между []

var sample = "this is an [hello] amazing [world]"

var regex = NSRegularExpression(pattern: "\[.+?\]"
, options: NSRegularExpressionOptions.CaseInsensitive 
, error: nil)

var matches = regex?.matchesInString(sample, options: nil
, range: NSMakeRange(0, countElements(sample))) as Array<NSTextCheckingResult>

for match in matches {
   let r = (sample as NSString).substringWithRange(match.range)//cast to NSString is required to match range format.
    println("found= \(r)")
}

Comments

    Ничего не найдено.