最近在完善Electron項(xiàng)目中的全文搜索功能,遇到了不少坑,大多與正則有關(guān)。因正則這塊兒一直比較弱,干脆總結(jié)一下。
Sql通配符
全文搜索必然會用到sql查詢中的關(guān)鍵字like,于是不用想就能寫出下面的查詢語句:
'select * FROM \'tbl_msg\' WHERE content like \'%'+key+'%\''
然后測試就反饋問題了: 在搜索的關(guān)鍵字中,帶有%,_時,會搜出很多不相干的內(nèi)容。
因?yàn)?和_是sql語句中的通配符,傳入上面的語句中,不會被識別為文本去匹配,需要添加轉(zhuǎn)義符。
因搜索的關(guān)鍵字是變量,需要將關(guān)鍵字中的通配符替換為加上轉(zhuǎn)義字符前綴,這里轉(zhuǎn)義字符可以通過ESCAPE關(guān)鍵字去定義。
// 執(zhí)行sql查詢前,替換關(guān)鍵字
const escapeTexts = ['%', '_'];
_.each(escapeTexts, item => {
searchKey = String(searchKey).replace(new RegExp(item, 'g'), `\\${item}`);
});
`'select * FROM \'tbl_msg\' WHERE content like \'%'+key+'%\'' ESCAPE \'\\\'`
我們定義了轉(zhuǎn)義符\,對關(guān)鍵字中出現(xiàn)的%和_進(jìn)行替換。
通訊錄搜索
全文搜索包含了對本地?cái)?shù)據(jù)庫和服務(wù)器中用戶列表的搜索,這種搜索場景下,關(guān)鍵字中的標(biāo)點(diǎn)符號和特殊字符沒有意義,需要在搜索前進(jìn)行過濾。
// 去除文本中的特殊字符和標(biāo)點(diǎn)
escapePunctuation(text) {
return text.replace(/[\ |\~|\`|\!|\@|\#|\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\||\\|\[|\]|\{|\}|\;|\:|\"|\'|\,|\<|\.|\>|\/|\?]/g, '');
},
關(guān)鍵字高亮
在展示搜索結(jié)果時,需要對匹配內(nèi)容進(jìn)行高亮。在jsx中,將匹配內(nèi)容替換為html字符串即可實(shí)現(xiàn),但若只這么寫,會有諸多問題。
- 特殊字符的轉(zhuǎn)義
這里使用了lodash提供的escapeRegExp方法 - 大小寫的替換
搜索匹配時,我們應(yīng)忽略大小寫的區(qū)分,但在高亮替換時,不能直接用搜索關(guān)鍵字去替換,這樣會導(dǎo)致原文的大小寫被替換。
text = text.replace(/keyReg/gi, (keyText) => `<span class="highlight">${keyText}</span>`)
- html轉(zhuǎn)jsx
因關(guān)鍵字高亮?xí)⑽谋巨D(zhuǎn)換為帶有html的字符串,在寫jsx時,需要使用dangerouslySetInnerHTML屬性,這樣顯然不利于封裝和組件復(fù)用。建議通過htmr之類的庫進(jìn)行直接轉(zhuǎn)換,生成react node。 - 文本中特殊字符轉(zhuǎn)換
使用htmr前,需要考慮待替換文本中有特殊字符的可能,尤其是<>,例如<message>123<message>這樣的文本經(jīng)htmr轉(zhuǎn)換后,jsx會將<message>識別為html標(biāo)簽,導(dǎo)致報(bào)錯。因此,高亮關(guān)鍵字的第一步應(yīng)該是替換特殊字符。
const escapeHtml = (html) => {
return String(html)
.replace(/&(?!\w+;)/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
const convertHtmlWithSearchKey = (text, searchKey, noWrap) => {
text = escapeHtml(text);
const keyReg = new RegExp(escapeRegExp(searchKey), 'gi');
text = text.replace(keyReg, (keyText) => `<span class="highlight">${keyText}</span>`);
if (!noWrap) {
text = text.replace(/(\r\n)|\n|\r/g, '<br>');
}
return convert(text);
},