ios - matches - swift 4 regular expression example
Partidos de expresiones regulares de extracto rรกpido (11)
Quiero extraer subcadenas de una cadena que coincida con un patrón regex.
Entonces estoy buscando algo como esto:
func matchesForRegexInText(regex: String!, text: String!) -> [String] {
???
}
Entonces esto es lo que tengo:
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 ...
}
El problema es que
matchesInString
me entrega una matriz de
NSTextCheckingResult
, donde
NSTextCheckingResult.range
es de tipo
NSRange
.
NSRange
es incompatible con
Range<String.Index>
, por lo que me impide usar
text.substringWithRange(...)
¿Alguna idea de cómo lograr esto tan rápido sin demasiadas líneas de código?
La forma más rápida de devolver todos los partidos y capturar grupos en Swift 5
extension String {
func match(_ regex: String) -> [[String]] {
let nsString = self as NSString
return (try? NSRegularExpression(pattern: regex, options: []))?.matches(in: self, options: [], range: NSMakeRange(0, count)).map { match in
(0..<match.numberOfRanges).map { match.range(at: $0).location == NSNotFound ? "" : nsString.substring(with: match.range(at: $0)) }
} ?? []
}
}
Devuelve una matriz de cadenas de 2 dimensiones:
"prefix12suffix fix1su".match("fix([0-9]+)su")
devoluciones...
[["fix12su", "12"], ["fix1su", "1"]]
// First element of sub-array is the match
// All subsequent elements are the capture groups
@ p4bloch si desea capturar resultados de una serie de paréntesis de captura, debe usar el
rangeAtIndex(index)
de
NSTextCheckingResult
, en lugar de
range
.
Aquí está el método de @MartinR para Swift2 desde arriba, adaptado para paréntesis de captura.
En la matriz que se devuelve, el primer resultado
[0]
es la captura completa, y luego los grupos de captura individuales comienzan desde
[1]
.
Comenté la operación del
map
(por lo que es más fácil ver lo que cambié) y lo reemplacé por bucles anidados.
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( $0.range )} //rangeAtIndex(0)
} catch let error as NSError {
print("invalid regex: /(error.localizedDescription)")
return []
}
}
Un ejemplo de uso podría ser, digamos que desea dividir una cadena del
title year
del
title year
por ejemplo, "Finding Dory 2016", podría hacer esto:
print ( matches(for: "^(.+)//s(//d{4})" , in: "Finding Dory 2016"))
// ["Finding Dory 2016", "Finding Dory", "2016"]
Así es como lo hice, espero que traiga una nueva perspectiva de cómo funciona esto en Swift.
En este ejemplo a continuación, obtendré cualquier cadena entre
[]
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)")
}
Descubrí que la solución de la respuesta aceptada desafortunadamente no se compila en Swift 3 para Linux. Aquí hay una versión modificada, entonces, que sí:
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: $0.range) }
} catch let error {
print("invalid regex: /(error.localizedDescription)")
return []
}
}
Las principales diferencias son:
-
Swift en Linux parece requerir que se suelte el prefijo
NS
en los objetos Foundation para los que no existe un equivalente nativo de Swift. (Ver la propuesta de evolución rápida # 86. ) -
Swift en Linux también requiere especificar los argumentos de
options
tanto para la inicialización deRegularExpression
como para el método dematches
. -
Por alguna razón, coaccionar un
String
en unNSString
no funciona en Swift en Linux, sino inicializar un nuevoNSString
con unString
ya que la fuente sí funciona.
Esta versión también funciona con Swift 3 en macOS / Xcode con la única excepción de que debe usar el nombre
NSRegularExpression
lugar de
RegularExpression
.
Esta es una solución muy simple que devuelve una matriz de cadenas con las coincidencias.
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: $0.range)
}
}
Incluso si el método
matchesInString()
toma una
String
como primer argumento, funciona internamente con
NSString
, y el parámetro de rango debe proporcionarse usando la longitud
NSString
y no como la longitud de la cadena Swift.
De lo contrario, fallará para "grupos de grafemas extendidos" como "banderas".
A partir de
Swift 4
(Xcode 9), la biblioteca estándar de Swift proporciona funciones para convertir entre
Range<String.Index>
y
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($0.range, in: text)!])
}
} catch let error {
print("invalid regex: /(error.localizedDescription)")
return []
}
}
Ejemplo:
let string = "๐ฉ๐ช€4€9"
let matched = matches(for: "[0-9]", in: string)
print(matched)
// ["4", "9"]
Nota: ¡
El
Range($0.range, in: text)!
desenvolvimiento forzado
Range($0.range, in: text)!
es seguro porque
NSRange
refiere a una subcadena del
text
cadena dado.
Sin embargo, si desea evitarlo, use
return results.flatMap {
Range($0.range, in: text).map { String(text[$0]) }
}
en lugar.
(Respuesta anterior para Swift 3 y anteriores :)
Por lo tanto, debe convertir la cadena Swift dada en una
NSString
y luego extraer los rangos.
El resultado se convertirá en una matriz de cadenas Swift automáticamente.
(El código para Swift 1.2 se puede encontrar en el historial de edición).
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($0.range)}
} catch let error as NSError {
print("invalid regex: /(error.localizedDescription)")
return []
}
}
Ejemplo:
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: $0.range)}
} catch let error {
print("invalid regex: /(error.localizedDescription)")
return []
}
}
Ejemplo:
let string = "๐ฉ๐ช€4€9"
let matched = matches(for: "[0-9]", in: string)
print(matched)
// ["4", "9"]
La mayoría de las soluciones anteriores solo dan la coincidencia completa como resultado ignorando los grupos de captura, por ejemplo: ^ / d + / s + (/ d +)
Para obtener las coincidencias del grupo de captura como se esperaba, necesita algo como (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
}
}
Mi respuesta se basa en las respuestas dadas, pero hace que la coincidencia de expresiones regulares sea más sólida al agregar soporte adicional:
- No solo devuelve coincidencias, sino que también devuelve todos los grupos de captura para cada coincidencia (ver ejemplos a continuación)
- En lugar de devolver una matriz vacía, esta solución admite coincidencias opcionales
-
Evita
do/catch
al no imprimir en la consola y hace uso de la construcción deguard
-
Agrega
matchingStrings
como una extensión aString
Swift 4.2
//: Playground - noun: a place where people can play
import Foundation
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.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))
return results.map { result in
(0..<result.numberOfRanges).map {
result.range(at: $0).location != NSNotFound
? nsString.substring(with: result.range(at: $0))
: ""
}
}
}
}
"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 3
//: Playground - noun: a place where people can play
import Foundation
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.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))
return results.map { result in
(0..<result.numberOfRanges).map {
result.rangeAt($0).location != NSNotFound
? nsString.substring(with: result.rangeAt($0))
: ""
}
}
}
}
"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($0).location != NSNotFound
? nsString.substringWithRange(result.rangeAtIndex($0))
: ""
}
}
}
}
Muchas gracias a su answer por capturar grupos y partidos completos con Swift 4 , lo que me ayudó mucho. También hice una adición para las personas que quieren una respuesta error.localizedDescription cuando su expresión regular no es válida:
extension String {
func matchingStrings(regex: String) -> [[String]] {
do {
let regex = try NSRegularExpression(pattern: regex)
let nsString = self as NSString
let results = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))
return results.map { result in
(0..<result.numberOfRanges).map {
result.range(at: $0).location != NSNotFound
? nsString.substring(with: result.range(at: $0))
: ""
}
}
} catch let error {
print("invalid regex: /(error.localizedDescription)")
return []
}
}
}
Para mí, tener la Descripción localizada como error me ayudó a comprender qué salió mal al escapar, ya que muestra qué final regex swift intenta implementar.
Si desea extraer subcadenas de una Cadena, no solo la posición (sino la Cadena real, incluidos los emojis). Entonces, lo siguiente quizás sea una solución más simple.
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]()
}
}
}
Ejemplo de uso:
"someText ๐ฟ๐
๐ฟโฝ๏ธ pig".regex("๐ฟโฝ๏ธ")
Devolverá lo siguiente:
["๐ฟโฝ๏ธ"]
Nota usando "/ w +" puede producir un inesperado ""
"someText ๐ฟ๐
๐ฟโฝ๏ธ pig".regex("//w+")
Devolverá esta matriz de cadenas
["someText", "๏ธ", "pig"]
Swift 4 sin NSString.
extension String {
func matches(regex: String) -> [String] {
guard let regex = try? NSRegularExpression(pattern: regex, options: [.caseInsensitive]) else { return [] }
let matches = regex.matches(in: self, options: [], range: NSMakeRange(0, self.count))
return matches.map { match in
return String(self[Range(match.range, in: self)!])
}
}
}