あらすじ
下記のような処理をつくる必要がありました。
- ひらがな・カタカナどちらでもヒットする曖昧検索をおこなう
- ヒットしたテキストを、正規表現を使って置換する
(ちなみに具体的には、長文の中からユーザーが入力した検索ワードをあいまい検索し、ヒットした箇所を<span>
で囲んだ状態の文字列に変換し、強調表示させる、というタスクでした。)
さてどうやって実装したもんか、と参考記事をググってみましたが、
ドンピシャな記事はなかなか出てきませんでした。
そのかわり「正規表現であいまい検索をする方法」みたいな記事はいくつかでてきました。
それは「検索対象と検索ワードをそれぞれひらがな(orカタカナ)に変換して、正規チェックする」という内容でした。
(
つまり検索対象が私の名前はゴンザレスたろうです
という文で、
検索ワードがゴンザレスタロウ
だった場合、
検索対象を私の名前はごんざれすたろうです
に変換、
検索ワードもごんざれすたろう
に変換した上で
正規表現で検索する、という方法です。
)
素直さがウリである(?)わたしは、言われたとおりにコードを書いてみたのですが、
このような方法だと、ヒットしたテキストを正規置換に利用しようとしたとき、結構複雑な処理が必要でした。
なぜなら、
ヒットしたテキストは、すでにひらがなに変換されているものなので、
ひらがな・カタカナ表記をそのままの状態で置換をしたい場合、
文章の何文字目から何文字目がヒットしていたのかを判定し、
元々の文章から該当する箇所の表記をひっぱってくる必要がありました。
しかも、JavaScriptには、正規マッチした文字列をすべて一気に置換してくれるような関数や、
マッチしている箇所のインデックス番号を取得するような関数が存在しないので、
ヒットしたテキストを1つ1つ置換しつつ、
置換後の文字列と置換前の文字列のインデックスのズレも考慮して置換をしていく必要があったのです。
(
たとえば正規置換で、私の名前はゴンザレスたろうです
を
私の名前は<span>ゴンザレスたろう</span>です
に変換したい場合、
置換によって追加される13文字のインデックスのズレを加味したうえで
ひきつづき置換処理を続ける必要がありました。
(ヒットする箇所が1箇所だけではない可能性があるので。)
)
絶望に暮れ、悠久とも思える10分もの間悩んでいたわたしに、
発想の女神の祝福が舞い降りました。
検索対象を置換するんじゃなくて、そもそも正規表現をひらがなでもカタカナでもヒットする形にすれば、とってもシンプルになる!
そのことに気づいたわたしは、早速コードを書きはじめました・・・。
コード
function katakanaToHiragana(src) {
return src.replace(/[\u30a1-\u30f6]/g, function(match) {
const chr = match.charCodeAt(0) - 0x60;
return String.fromCharCode(chr);
});
}
function hiraganaToKatagana(src) {
return src.replace(/[\u3041-\u3096]/g, function(match) {
const chr = match.charCodeAt(0) + 0x60;
return String.fromCharCode(chr);
});
}
function escapeRegExp(string) {
return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
}
function generateFuzzyRegExp(searchWord) {
searchWord = escapeRegExp(searchWord);
const chars = searchWord.split('').map(char=>{
const hiragana = this.katakanaToHiragana( char );
const katakana = this.hiraganaToKatagana( char );
if( hiragana===katakana ) return char;
return `(${hiragana}|${katakana})`;
});
const fuzzyRegExp = new RegExp( `(${chars.join('')})`, 'ig' );
return fuzzyRegExp;
}
説明
katakanaToHiragana
hiraganaToKatakana
カタカナをひらがなに、ひらがなをカタカナに変換する関数です。
こちらのリポジトリを参考にさせていただきました。
escapeRegexp
正規表現と認識されてしまう文字をエスケープする関数です。
こちらの記事を参考にさせていただきました。
generateFuzzyRegExp
引数に渡された文字列から、
カタカナ・ひらがな・大文字・小文字を区別しない正規表現を生成する関数です。
エスケープ後の文字列を1文字ずつ分割し、
(あ|ア)
のようなパターンに変換して配列に格納しています。
joinで1つの文字列にまとめたら、
まとめて $1 で拾えるようにカッコで囲ってあげて、
正規表現に変換してあげれば完成です!
(正規表現のiオプションによって、英大文字・英小文字は自動的に区別しない仕様となります。)
感想
技術レベル的にはたいしたことないですが、
発想の転換って大事だな〜と思いました。