swiftで正規表現使って抽出と置換をする
swiftで正規表現使って抽出する方法とか調べてみたらなんか意外にスッキリした方法が見つからなかったので、
やりかたの備忘録てきなものです。
モダンを謳ってるswiftのことだから正規表現用の素敵メソッドみたいのあるんでしょ!
とか思ってたらありませんでした。。。
モダンっぽくできるような事ができるものを作ってる人もいるにはいました。
いやはやありがたやありがたや。
ですが、ライブラリっぽいのをいれるのはなんか重くなったりするかなぁとか思ったりもしたので
標準のやつだけで正規表現できないかと調べてみました。
抽出編
調べてみると案外昔馴染みの方法でやるしかないかなぁといった印象だったので、
昔のコードをswiftに置き換えてみました。
iOS アプリ開発での正規表現を使った文字列処理がややこしい
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// NSString *body; に文字列が入っているものとする。 NSError *error = nil; NSRegularExpression *regexp = [NSRegularExpression regularExpressionWithPattern:@"(\\d+.dat)::" options:0 error:&error]; </code> NSMutableArray *dats = [[NSMutableArray alloc] init]; id proc = ^(NSTextCheckingResult *arr, NSMatchingFlags flag, BOOL *stop) { [dats addObject: [body substringWithRange:[arr rangeAtIndex:1]]]; }; <code> [regexp enumerateMatchesInString:body options:0 range:NSMakeRange(0, body.length) usingBlock:proc]; |
先人の知恵を借りつつ今回は
1 |
[test|hoge|hoge123] [test|huge|huge456] |
という文字列から、hoge、hoge123, huge, huge456を抽出するようなコードを書きました。
1 2 3 4 5 6 7 8 9 10 |
let pattern = "\\[test\\|([a-z0-9]+)\\|([a-z0-9]+)\\]" let content:NSString = "[test|hoge|hoge123] [test|huge|huge456]" let regex = NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions.CaseInsensitive, error: nil) regex?.enumerateMatchesInString(content, options: nil, range: NSMakeRange(0, content.length), usingBlock: {(result: NSTextCheckingResult!, flags: NSMatchingFlags, stop: UnsafeMutablePointer<ObjCBool>) -> Void in for i in 0...result.numberOfRanges - 1 { let range = result.rangeAtIndex(i) println(content.substringWithRange(range)) } }) |
出力結果は以下の感じ
1 2 3 4 5 6 |
[test|hoge|hoge123] hoge hoge123 [test|huge|huge456] huge huge456 |
コツは、検索する対象をNSStringにしないといけないところ。
1 |
let content:NSString = "[test|hoge|hoge123] [test|huge|huge456]" |
result.rangeAtIndexの返り値がNSRangeでかえってくるんだけど、StringのsubstringWithRangeはRangeを求めてくるんす。
なんだかイマイチ行けてない感はしますが、求めてくるんだからしょうがない。
それともStringでやる方法が別にあるのかもしれないですが、、、
誰かエロイ人知ってたら教えて下さい。
追記:
おまけに書いてる本を書いた神からStringでもかけるよとの導きを頂きました。
1 2 3 4 5 6 7 8 9 10 11 12 |
let pattern = "\\[test\\|([a-z0-9]+)\\|([a-z0-9]+)\\]" let content = "[test|hoge|hoge123] [test|huge|huge456]" let regex = NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions.CaseInsensitive, error: nil) regex?.enumerateMatchesInString(content, options: nil, range: NSMakeRange(0, countElements(content)), usingBlock: {(result: NSTextCheckingResult!, flags: NSMatchingFlags, stop: UnsafeMutablePointer<ObjCBool>) -> Void in for i in 0...result.numberOfRanges - 1 { let range = result.rangeAtIndex(i) let startIndex:String.Index = advance(content.startIndex, range.location) let endIndex = advance(content.startIndex, NSMaxRange(range)) println(content[startIndex..<endIndex]) } }) |
だがしかしコードが長くなるのでObj-cの力を借りたくないというこだわりがない限りはNSStringのが楽そうです。
ちなみにStringにした場合はcontent.lengthがないので代わりにcountElements(content)を使ってます。
置換編
正規表現して取ってきた文字列を置換したいって事が多いですよね。
置換は案外簡単にさくっといけちゃいます。
抽出した結果をforとかで回さなくていいんですよ奥さん!!
今回は[test|hoge|hoge123] と[]に囲まれてるところをtest:hoge:hoge123とかに置換してみます。
1 2 3 4 5 |
let pattern = "\\[test\\|([a-z0-9]+)\\|([a-z0-9]+)\\]" let content2 = "[test|hoge|hoge123] [test|huge|huge456]" let replace = "test:$1:$2" let replaceString = content2.stringByReplacingOccurrencesOfString(pattern, withString: replace, options: NSStringCompareOptions.RegularExpressionSearch, range: nil) println(replaceString) |
出力結果
1 |
test:hoge:hoge123 test:huge:huge456 |
今度は普通にStringでいけちゃいます。
コツは
1 |
let replace = "test:$1:$2" |
$1とかで正規表現でマッチした結果を呼び出せます。
おわり
以外に調べたら正規表現苦戦しちゃいました。
正規表現はコードのほうがいつも考えるの苦戦しますが、書き方に苦戦しちゃいました。。
誰かの助けになればいいとおもいます。
おまけ
ちなみに弊社の神が書かれたsiwft本売ってます。
開発のプロが教える Swift標準ガイドブック
こちらにはswitchで正規表現を簡単に出来る方法なども載っているので気になる方は是非購入してみてください。
おまけ2
正規表現使うときは僕はここにお世話になってます。