Swift extract regex coincide

Quiero extraer subcadenas de una cadena que coincida con un patrón de expresión regular.

Así que 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 /// ??? return ... } 

El problema es que matchesInString me entrega una matriz de NSTextCheckingResult , donde NSTextCheckingResult.range es de tipo NSRange .

NSRange es incompatible con Range , por lo que me impide usar text.substringWithRange(...)

¿Alguna idea de cómo lograr esto simple en poco tiempo sin demasiadas líneas de código?

Incluso si el método matchesInString() toma una String como primer argumento, funciona internamente con NSString , y el parámetro de rango debe darse utilizando la longitud NSString y no como la longitud de la cadena Swift. De lo contrario, fallará para “clústeres de grafemas extendidos” como “banderas”.

A partir de Swift 4 (Xcode 9), la biblioteca estándar Swift proporciona funciones para convertir entre Range 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)! desenvoltura forzada Range($0.range, in: text)! es seguro porque NSRange refiere a una subcadena del text cadena dado. Sin embargo, si quiere evitarlo, use

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

en lugar.


(Respuesta anterior para Swift 3 y antes 🙂

Entonces debe convertir la cadena Swift dada a un 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"] 

Mi respuesta se basa en las respuestas dadas, pero hace que la comparación de expresiones regulares sea más sólida al agregar soporte adicional:

  • Devuelve no solo coincide 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 de guard
  • Agrega matchingStrings como una extensión a String

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.. 

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.. 

Si desea extraer subcadenas de una Cadena, no solo la posición, (sino la Cadena real incluidos los emojis). Entonces, la siguiente puede ser 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é este conjunto de cadenas

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

Descubrí que la solución de la respuesta aceptada desafortunadamente no se comstack en Swift 3 para Linux. Aquí hay una versión modificada, entonces, 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:

  1. Swift en Linux parece requerir que se elimine el prefijo NS en los objetos Foundation para los cuales no hay un equivalente nativo de Swift. (Véase la propuesta de evolución Swift n . ° 86 ).

  2. Swift en Linux también requiere especificar los argumentos de options para la inicialización de RegularExpression y el método de matches .

  3. Por alguna razón, la coerción de un String en un NSString no funciona en Swift en Linux sino que inicializa un nuevo NSString con un String medida que la fuente 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 .

@ p4bloch si desea capturar los resultados de una serie de paréntesis de captura, entonces necesita usar el rangeAtIndex(index) de NSTextCheckingResult , en lugar del range . Aquí está el método de @MartinR para Swift2 desde arriba, adaptado para capturar paréntesis. 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 la reemplacé con bucles nesteds.

 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.. 

Un ejemplo de caso de uso podría ser, por ejemplo, si 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"] 

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) } } 

La mayoría de las soluciones anteriores solo dan la coincidencia completa, como resultado, ignoran 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 } } 

Así es como lo hice, espero que brinde una nueva perspectiva sobre 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 for match in matches { let r = (sample as NSString).substringWithRange(match.range)//cast to NSString is required to match range format. println("found= \(r)") }