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 без слишком много строк кода?
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 [] } }основные отличия:
Swift на Linux, кажется, требует удаления
NSпрефикс для объектов Foundation, для которых нет эквивалента Swift-native. (См.Swift evolution proposal #86.)Swift на Linux также требует указание
optionsаргументы для обоихRegularExpressionинициализации иmatchesметод.по какой-то причине, принуждая 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