Je veux extraire des sous-chaînes d'une chaîne qui correspond à un motif regex.
Donc je cherche quelque chose comme ça:
func matchesForRegexInText(regex: String!, text: String!) -> [String] {
???
}
Donc voici ce que j'ai:
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 ...
}
Le problème est que matchesInString
me fournit un tableau de NSTextCheckingResult
, où NSTextCheckingResult.range
est de type NSRange
.
NSRange
est incompatible avec Range<String.Index>
, ce qui m'empêche d'utiliser text.substringWithRange(...)
Avez-vous une idée de la façon de réaliser cette chose simple dans Swift sans trop de lignes de code?
Même si la méthode matchesInString()
prend un premier argument avec String
, elle fonctionne en interne avec NSString
, et le paramètre range doit être défini à l'aide de la longueur NSString
et non comme Swift longueur de chaîne. Sinon, cela échouera pour les "grappes de graphèmes étendues" telles que les "drapeaux".
À compter de Swift 4 (Xcode 9), la bibliothèque standard Swift fournit des fonctions permettant la conversion entre Range<String.Index>
et 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 []
}
}
Exemple:
let string = "????????€4€9"
let matched = matches(for: "[0-9]", in: string)
print(matched)
// ["4", "9"]
Remarque: Le déroulement forcé Range($0.range, in: text)!
est sécurisé, car NSRange
fait référence à une sous-chaîne de la chaîne donnée text
. Cependant, si vous voulez l’éviter, utilisez
return results.flatMap {
Range($0.range, in: text).map { String(text[$0]) }
}
au lieu.
(Ancienne réponse pour Swift 3 et versions antérieures:)
Vous devez donc convertir la chaîne Swift donnée en une chaîne NSString
, puis extraire les plages. Le résultat sera automatiquement converti en un tableau de chaînes Swift.
(Le code pour Swift 1.2 se trouve dans l'historique d'édition.)
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 []
}
}
Exemple:
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 []
}
}
Exemple:
let string = "????????€4€9"
let matched = matches(for: "[0-9]", in: string)
print(matched)
// ["4", "9"]
Ma réponse s'appuie sur les réponses données mais rend la correspondance des expressions rationnelles plus robuste en ajoutant un support supplémentaire:
do/catch
en n'imprimant pas sur la console et tilise la construction guard
matchingStrings
en tant que extension à String
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
//: 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))
: ""
}
}
}
}
Si vous souhaitez extraire des sous-chaînes d'une chaîne, pas seulement la position (mais également la chaîne, y compris les émojis). Ensuite, ce qui suit peut-être une solution plus 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]()
}
}
}
Exemple d'utilisation:
"someText ????????????⚽️ pig".regex("????⚽️")
Retournera ce qui suit:
["????⚽️"]
Notez que l'utilisation de "\ w +" peut produire un "" inattendu
"someText ????????????⚽️ pig".regex("\\w+")
Renverra ce tableau de chaînes
["someText", "️", "pig"]
J'ai trouvé que la solution de réponse acceptée ne compilait malheureusement pas sur Swift 3 pour Linux. Voici une version modifiée, alors, qui fait:
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 []
}
}
Les principales différences sont les suivantes:
Swift sur Linux semble nécessiter de supprimer le préfixe NS
sur les objets Foundation pour lesquels il n’existe pas d’équivalent natif de Swift. (Voir Proposition d'évolution Swift n ° 86 .)
Swift sur Linux nécessite également de spécifier les arguments options
pour l’initialisation RegularExpression
et la méthode matches
.
Pour une raison quelconque, contraindre un String
dans un NSString
ne fonctionne pas dans Swift sous Linux, mais initialise un nouveau NSString
avec un String
comme la source fonctionne.
Cette version fonctionne également avec Swift 3 sur macOS/Xcode à la seule exception que vous devez utiliser le nom NSRegularExpression
au lieu de RegularExpression
.
@ p4bloch Si vous souhaitez capturer les résultats d'une série de parenthèses de capture, vous devez utiliser la méthode rangeAtIndex(index)
de NSTextCheckingResult
au lieu de range
. Voici la méthode de @MartinR pour Swift2 vue du dessus, adaptée pour les parenthèses de capture. Dans le tableau renvoyé, le premier résultat [0]
correspond à la capture complète, puis les groupes de capture individuels commencent à partir de [1]
. J'ai commenté l'opération map
(il est donc plus facile de voir ce que j'ai changé) et je l'ai remplacée par des boucles imbriquées.
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 []
}
}
Voici un exemple de cas d'utilisation, par exemple, si vous souhaitez scinder une chaîne de title year
, par exemple "Finding Dory 2016", procédez comme suit:
print ( matches(for: "^(.+)\\s(\\d{4})" , in: "Finding Dory 2016"))
// ["Finding Dory 2016", "Finding Dory", "2016"]
La plupart des solutions ci-dessus donnent uniquement la correspondance complète, ignorant ainsi les groupes de capture, par exemple: ^\d +\s + (\ d +)
Pour obtenir les correspondances de groupe de capture comme prévu, vous avez besoin de quelque chose comme (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
}
}
C’est ce que j’ai fait. J’espère que cela apportera une nouvelle perspective à la manière dont cela fonctionne sur Swift.
Dans cet exemple ci-dessous, je vais obtenir une chaîne quelconque 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)")
}
C'est une solution très simple qui retourne un tableau de chaînes avec les correspondances
Rapide 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)
}
}
Swift 4 sans 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)!])
}
}
}
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)) }
} ?? []
}
}
retourne un tableau à 2 dimensions de chaînes:
"prefix12suffix fix1su".match("fix([0-9]+)su")
retourne ...
[["fix12su", "12"], ["fix1su", "1"]]
// First element of sub-array is the match
// All subsequent elements are the capture groups
Un grand merci à Lars Blumberg his réponse pour la capture de groupes et de matches complets avec Swift 4 , qui m'a beaucoup aidé. J'y ai également ajouté un complément pour les personnes qui souhaitent une réponse error.localizedDescription lorsque leur expression rationnelle est invalide:
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 []
}
}
}
Pour moi, avoir localisé LocalDescription comme erreur m'a permis de comprendre ce qui n'allait pas avec l'échappement, car il indique quelle expression rationnelle finale Swift essaie de mettre en œuvre.