Javascript RegExp untuk menerapkan tag span pada string dengan substring bersarang
Contoh String:
'There is a red car parked in front of a blue house with a fence painted red.'
String yang akan disorot dengan span adalah:
['red car', 'blue house', 'red'].
String yang diharapkan:
There is a <span class='redHighlight'>red car</span> parked in front of a <span class='blueHighlight'>blue house</span> with a fence painted <span class='redHighlight'>red</span>.
Namun ketika saya melakukan penggantian dengan iterasi pada array, saya berakhir dengan tag span bersarang.
> "There is a <span class='<span class='redHighlight'>red</span>Highlight'><span class='redHighlight'>red</span> car</span> parked in front of a <span class='blueHighlight'>blue house</span> with a fence painted <span class='redHighlight'>red</span>.
Kode:
let strToHighlight = 'There is a red car parked in front of a blue house with a fence painted red.';
let stringsToMatch = [{'strVal': 'red car',
'cssClass': 'redHighlight'},
{'strVal': 'blue house',
'cssClass': 'blueHighlight'},
{'strVal': 'red',
'cssClass':'redHighlight'}
];
stringsToMatch.forEach(el => {
let regEx = new RegExp(el.strVal,'g') // replace all occurances
strToHighlight = strToHighlight.replace(regEx, `<span class='${el.cssClass}'>${el.strVal}</span>}`);
console.log(strToHighlight);
})
Adakah saran tentang cara menghindari penandaan ulang string baik melalui RegEx atau metode lain?
EDIT: Setiap string harus disorot dengan kelas gaya yang berbeda. Mengedit larik strToMatch menjadi larik objek yang menyimpan nama kelas CSS untuk diterapkan.
Jawaban
Anda perlu mengurutkan stringsToMatchmenurut panjang dalam urutan menurun dan menggunakan pola berbasis pergantian tunggal (dengan batas kata akhirnya hanya mencocokkan seluruh kata) untuk memastikan penggantian dilakukan sekaligus:
let strToHighlight = 'There is a red car parked in front of a blue house with a fence painted red.';
let stringsToMatch = [{'strVal': 'red car', 'cssClass': 'redHighlight'},
{'strVal': 'blue house', 'cssClass': 'blueHighlight'},
{'strVal': 'red','cssClass':'redHighlight'}
];
const searchTerms = stringsToMatch.map(x => x.strVal);
searchTerms.sort((a, b) => b.length - a.length);
let regEx = new RegExp(String.raw`\b(?:${searchTerms.join('|')})\b`,'g'); // => /\b(?:blue house|red car|red)\b/g strToHighlight = strToHighlight.replace(regEx, (m) => `<span class='${stringsToMatch.find(x => x.strVal == m).cssClass}'>${m}</span>`);
console.log(strToHighlight);
Keluaran:
There is a <span class='redHighlight'>red car</span> parked in front of a <span class='blueHighlight'>blue house</span> with a fence painted <span class='redHighlight'>red</span>.
Sini,
stringsToMatch.sort((a, b) => b.length - a.length);mengurutkan string dalam array menurut panjangnya dalam urutan menurun. Lihat mengapa penting dalam Remember That The Regex Engine Is Eagernew RegExp(String.raw`\b(?:${stringsToMatch.join('|')})\b`,'g')membuat objek RegExp, dengan\b(?:blue house|red car|red)\bpola (lihat demo ).replace(regEx, "<span class='highlight_text'>$&</span>")mengganti kecocokan dengan dirinya sendiri diapit denganspantag ($&adalah referensi kembali ke seluruh nilai kecocokan).
Anda bisa mengganti (mengurangi) semua kata yang cocok dengan tanda posisi dan kemudian mengganti semua tanda tersebut dengan versi yang dibungkus.
Ini dapat digunakan di Node JS, karena ada deteksi browser melalui typeof window !== 'undefined'saat memeriksa elemen.
/**
* @param {String|Node} strOrElement - String or element (browser only)
* @param {String[]} words - List of words to wrap
* @param {function} replacerFn - Replacer function for each word
*/
const wrapWords = (strOrElement, words, replacerFn) => {
const isEl = typeof window !== 'undefined' && strOrElement instanceof Node;
if (!isEl && typeof strOrElement !== 'string') {
throw new Error('must be text or an element');
}
const sorted = words.slice().sort().reverse();
const text = isEl ? strOrElement.textContent : strOrElement;
const result = sorted
.reduce((curr, word, idx) => curr.replace(word, `$\{${idx}\}`), text)
.replace(/\$\{(\d+)\}/g, (m, p1) => replacerFn(sorted[parseInt(p1, 10)])); if (isEl) strOrElement.innerHTML = result; return result; }; const words = [ 'red car', 'blue house', 'red' ]; const el = document.querySelector('p') const fn = word => `<span class="highlight_text">${word}</span>`;
wrapWords(el, words, fn);
.highlight_text {
background: #FFA;
border: thin dashed red;
padding: 0.125em 0.25em;
}
<p>There is a red car parked in front of a blue house with a fence painted red.</p>
Kemungkinan diperpanjang
Jika Anda menginginkan campuran teks dan ekspresi reguler dalam "daftar kata", Anda dapat mengubah fungsi di atas untuk menyimpan setiap kecocokan. Dalam contoh ini, Anda membuat penggantian melalui cache, bukan daftar kata yang diurutkan terbalik.
/**
* @param {String|Node} strOrElement - String or element (browser only)
* @param {String[]} words - List of words to wrap
* @param {function} replacerFn - Replacer function for each word
*/
const wrapWords = (strOrElement, words, replacerFn) => {
const isEl = typeof window !== 'undefined' && strOrElement instanceof Node;
if (!isEl && typeof strOrElement !== 'string') {
throw new Error('must be text or an element');
}
const text = isEl ? strOrElement.textContent : strOrElement;
const cache = [];
const result = words.slice().sort().reverse()
.reduce((curr, word, idx) => curr.replace(word, (m) => {
cache[idx] = [ ...(cache[idx] || []), m ];
return `$\{${idx}\}`;
}), text)
.replace(/\$\{(\d+)\}/g, (m, p1) => { return replacerFn(cache[parseInt(p1, 10)].pop()); }); if (isEl) strOrElement.innerHTML = result; return result; }; const wl1 = [ 'red car', 'blue house', 'red' ]; const el1 = document.querySelector('p:nth-child(1)') wrapWords(el1, wl1, word => `<span class="highlight_text">${word}</span>`);
const wl2 = [ /\$\d+.\d+/g ]; const el2 = document.querySelector('p:nth-child(2)') wrapWords(el2, wl2, word => `<span class="highlight_text">${word}</span>`);
.highlight_text {
background: #FFA;
border: thin dashed red;
padding: 0.125em 0.25em;
}
<p>There is a red car parked in front of a blue house with a fence painted red.</p>
<p>The price of the watch was reduced from $500.00 down to $199.99.</p>