// // SwiftRegex.swift // SwiftRegex // // Created by John Holdsworth on 26/06/2014. // Copyright (c) 2014 John Holdsworth. // // $Id: //depot/SwiftRegex/SwiftRegex.swift#37 $ // // This code is in the public domain from: // https://github.com/johnno1962/SwiftRegex // import Foundation infix operator <~ { associativity none precedence 130 } private let lock = dispatch_semaphore_create(1) private var swiftRegexCache = [String: NSRegularExpression]() internal final class SwiftRegex: NSObject, BooleanType { var target:String var regex: NSRegularExpression init(target:String, pattern:String, options:NSRegularExpressionOptions?) { self.target = target if dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_MSEC))) != 0 { do { let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions.DotMatchesLineSeparators) self.regex = regex } catch let error as NSError { SwiftRegex.failure("Error in pattern: \(pattern) - \(error)") self.regex = NSRegularExpression() } super.init() return } if let regex = swiftRegexCache[pattern] { self.regex = regex } else { do { let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions.DotMatchesLineSeparators) swiftRegexCache[pattern] = regex self.regex = regex } catch let error as NSError { SwiftRegex.failure("Error in pattern: \(pattern) - \(error)") self.regex = NSRegularExpression() } } dispatch_semaphore_signal(lock) super.init() } private static func failure(message: String) { fatalError("SwiftRegex: \(message)") } private var targetRange: NSRange { return NSRange(location: 0,length: target.utf16.count) } private func substring(range: NSRange) -> String? { if range.location != NSNotFound { return (target as NSString).substringWithRange(range) } else { return nil } } func doesMatch(options: NSMatchingOptions!) -> Bool { return range(options).location != NSNotFound } func range(options: NSMatchingOptions) -> NSRange { return regex.rangeOfFirstMatchInString(target as String, options: [], range: targetRange) } func match(options: NSMatchingOptions) -> String? { return substring(range(options)) } func groups() -> [String]? { return groupsForMatch(regex.firstMatchInString(target as String, options: NSMatchingOptions.WithoutAnchoringBounds, range: targetRange)) } private func groupsForMatch(match: NSTextCheckingResult?) -> [String]? { guard let match = match else { return nil } var groups = [String]() for groupno in 0...regex.numberOfCaptureGroups { if let group = substring(match.rangeAtIndex(groupno)) { groups += [group] } else { groups += ["_"] // avoids bridging problems } } return groups } subscript(groupno: Int) -> String? { get { return groups()?[groupno] } set(newValue) { if newValue == nil { return } for match in Array(matchResults().reverse()) { let replacement = regex.replacementStringForResult(match, inString: target as String, offset: 0, template: newValue!) let mut = NSMutableString(string: target) mut.replaceCharactersInRange(match.rangeAtIndex(groupno), withString: replacement) target = mut as String } } } func matchResults() -> [NSTextCheckingResult] { let matches = regex.matchesInString(target as String, options: NSMatchingOptions.WithoutAnchoringBounds, range: targetRange) as [NSTextCheckingResult] return matches } func ranges() -> [NSRange] { return matchResults().map { $0.range } } func matches() -> [String] { return matchResults().map( { self.substring($0.range)!}) } func allGroups() -> [[String]?] { return matchResults().map { self.groupsForMatch($0) } } func dictionary(options: NSMatchingOptions!) -> Dictionary { var out = Dictionary() for match in matchResults() { out[substring(match.rangeAtIndex(1))!] = substring(match.rangeAtIndex(2))! } return out } func substituteMatches(substitution: ((NSTextCheckingResult, UnsafeMutablePointer) -> String), options:NSMatchingOptions) -> String { let out = NSMutableString() var pos = 0 regex.enumerateMatchesInString(target as String, options: options, range: targetRange ) {match, flags, stop in let matchRange = match!.range out.appendString( self.substring(NSRange(location:pos, length:matchRange.location-pos))!) out.appendString( substitution(match!, stop) ) pos = matchRange.location + matchRange.length } out.appendString(substring(NSRange(location:pos, length:targetRange.length-pos))!) return out as String } var boolValue: Bool { return doesMatch(nil) } } extension String { subscript(pattern: String, options: NSRegularExpressionOptions) -> SwiftRegex { return SwiftRegex(target: self, pattern: pattern, options: options) } } extension String { subscript(pattern: String) -> SwiftRegex { return SwiftRegex(target: self, pattern: pattern, options: nil) } } func <~ (left: SwiftRegex, right: String) -> String { return left.substituteMatches({match, stop in return left.regex.replacementStringForResult( match, inString: left.target as String, offset: 0, template: right ) }, options: []) }