Coincidencias de expresiones regulares de extracción rápida
Quiero extraer subcadenas de una cadena que coincida con un patrón de expresiones regulares.
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 eso 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 usartext.substringWithRange(...)
¿Alguna idea de cómo lograr esto simple rápidamente sin demasiadas líneas de código?
Incluso si el matchesInString()
método toma a String
como primer argumento, funciona internamente con NSString
, y el parámetro de rango debe proporcionarse utilizando la NSString
longitud y no la longitud de la cadena Swift. De lo contrario, fallará en el caso de "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 desenvolvimiento forzado Range($0.range, in: text)!
es seguro porque se NSRange
refiere a una subcadena de la cadena dada text
. Sin embargo, si desea evitarlo, utilice
return results.flatMap {
Range($0.range, in: text).map { String(text[$0]) }
}
en cambio.
(Respuesta anterior para Swift 3 y versiones anteriores :)
Por lo tanto, debes convertir la cadena Swift dada en NSString
y luego extraer los rangos. El resultado se convertirá automáticamente en una matriz de cadenas Swift.
(El código de Swift 1.2 se puede encontrar en el historial de ediciones).
Rápido 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"]
Rápido 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"]
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:
- Devuelve no sólo 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 laguard
construcción. - Se agrega
matchingStrings
como una extensión aString
Rápido 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")
veloz 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")
veloz 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))
: ""
}
}
}
}
La forma más rápida de devolver todas las coincidencias 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, nsString.length)).map { match in
(0..<match.numberOfRanges).map { match.range(at: $0).location == NSNotFound ? "" : nsString.substring(with: match.range(at: $0)) }
} ?? []
}
}
Devuelve una matriz bidimensional de cadenas:
"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
Si desea extraer subcadenas de una Cadena, no solo la posición (sino la Cadena real, incluidos los emojis). Entonces, la siguiente quizás sea una solución más sencilla.
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]()
}
}
}
Uso de ejemplo:
"someText 👿🏅👿⚽️ pig".regex("👿⚽️")
Devolverá lo siguiente:
["👿⚽️"]
Nota: el uso de "\w+" puede producir un "" inesperado
"someText 👿🏅👿⚽️ pig".regex("\\w+")
Devolverá esta matriz de cadenas
["someText", "️", "pig"]
Descubrí que, lamentablemente, la solución de la respuesta aceptada no se compila en Swift 3 para Linux. Entonces aquí hay una versión modificada que hace:
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 eliminar el
NS
prefijo en objetos Foundation para los cuales no existe un equivalente nativo de Swift. (Consulte la propuesta de evolución Swift n.° 86 ).Swift en Linux también requiere especificar los
options
argumentos tanto para laRegularExpression
inicialización como para elmatches
método.Por alguna razón, convertir a
String
en anNSString
no funciona en Swift en Linux, pero inicializar a newNSString
con aString
como fuente sí funciona.
Esta versión también funciona con Swift 3 en macOS/Xcode con la única excepción de que debes usar el nombre NSRegularExpression
en lugar de RegularExpression
.