#javascript #jquery
Хочу написать некий "антимат". Есть блоки, в которых находится текст, суть в том, чтобы "пройтись" по этим блокам, проверить текст на наличие "запрещённого слова" и заменить его на что-нибудь. Подумав, решил не делать какие-то замены в самом слове, а просто обернуть его в span и ему выставить, допустим filter: blur(5px). Вооот.. Соль в чём, в голове реализация такая.. "бэдвордс", цикл по блокам, цикл по "бэдвродс"у + поиск слова в тексте, после нахождения заменяем слово на слово.. Я задам это как вопрос, в котором хочу получить уже готовый код, но сам тоже буду пытаться написать. Хотя нет, даже не так, в общем некий конкурс, по окончанию которого, набравший больше "лайков" ответ получить 500 репы. По срокам, до 10.03.19 В общем, надеюсь что будет интересно и админы не закроют) Допустим всё выглядит так: let badword = [ 'Lorem', 'non', 'lectus', 'ligula', 'nisi' ];Lorem ipsum dolor sit amet, consectetur adipiscing elit.Suspendisse non pharetra mauris. Suspendisse a lacinia lacus. Nulla facilisi.Suspendisse eu lectus aliquam, porttitor est eleifend, blandit mauris.Nunc ut bibendum ligula, eu consequat odio. Praesent fermentum nisi a lobortis rhoncus. Phasellus vel metus eu dolor molestie porta.
Ответы
Ответ 1
Если контент блоков .item не предполагает вложенных тегов, то: не нужен массив; код сокращается до минимума; проверочные слова добавляются через вертикальная черту |; вместо целых проверочных слов, могут быть дополнительные условия выбора. let badword = '(Lorem|non|lectus|ligula|nisi)'; let reg = new RegExp(badword, 'gim'); $('.item').each(function() { let text = $(this).text(); text = text.replace(reg, '$1'); $(this).html(text); }); .bw { color: red; -webkit-filter: blur(3px); filter: blur(3px); }Если же допускается какое либо форматирование, тогда нужна чуть более сложная логика: var badword = 'Lorem|non|lectus|ligula|(про)?тест(им)?|nisi|imgur'; var reg = new RegExp('(^|[^\\wа-яё])(' + badword + ')(?![\\wа-яё])', 'gi'); function fAntiSwear(oElem) { [...oElem.contents()].forEach(function(oNode) { if (oNode.nodeType === Node.ELEMENT_NODE) { fAntiSwear($(oNode)); } else if (oNode.nodeType === Node.TEXT_NODE) { $(oNode).replaceWith($(oNode).text().replace(reg, '$1$2')); } }); } fAntiSwear($('.content')); .bw { color: red; -webkit-filter: blur(3px); filter: blur(3px); } .lectus { color: blue; }Lorem ipsum dolor sit amet, consectetur adipiscing elit.Suspendisse non pharetra mauris. Suspendisse a lacinia lacus. Nulla facilisi.Suspendisse eu lectus aliquam, porttitor est eleifend, blandit mauris.Nunc ut bibendum ligula, eu consequat odio. Praesent fermentum nisi a lobortis rhoncus. Phasellus vel metus eu dolor molestie porta.ИсточникLorem ipsum dolor sit amet, consecteturLorem adipiscing elit.Suspendisse non pharetra mauris. Suspendisse anon lacinia lacus. Nulla facilisi.Suspendisse eu lectus aliquam, porttitor est eleifend, blandit mauris.Nunc ut bibendum ligula, eu consequat odio. Praesent fermentum nisi a lobortis rhoncus. Phasellus vel metus eu dolor molestie porta.Протестим слово тест. Ну что, тестим этот протест против теста?Ответ 2
Итак, вот мой вариант. Как это работает? Чтобы найти в элементе и во ВСЕХ его дочерних элементах плохие слова нужно просто натравить функцию findBadWords() на нужный нам элемент. На вход 2 параметра: сам jq-элемент (например, body) и словарь (массив строк). Например findBadWords($("body"), ["дурак", "дебил", "пипец"]);. Код рекурсивно проходится по всем textNode и если находит в них что-то из словаря, то оборачивает это слово в span и заменяет предыдущую строку этой новой. (!!!) Код нуждается в доработках и в идеале должен быть переписан на ванильный js для производительности. const findBadWords = (el, dictionary) => { let contents = Array.from(el.contents()); // находим всех детей элемента, в том числе и текстовых contents.forEach(item => { // проверяем каждого ребенка if (item.nodeType === 1) { // если это не текст то.. findBadWords($(item), dictionary); // проверяем дальше его детей } else if (item.nodeType === 3) { // если это просто текст то.. let regexp = new RegExp(dictionary.join("|"), "gim"); // формируем регулярку из нашего словаря $(item).replaceWith(item.textContent.replace(regexp, m => `${m}`)); // заменяем найденный элемент нашим обработанным текстом } }); } $("#start").one("click", () => { findBadWords($("#root"), ["дурак", "дебил", "лошара", "пипец"]); }); .badword { color: red; font-weight: bold; }Дурак - плохое слово.
Дебил лучше тоже не говорить
Очень глубоко расположенный лошара
Как видите, коду не важно, где и как глубоко находится плохое слово. Если есть какие-то недочеты или предложения по улучшению - пишите в комментариях.Ответ 3
Мой ответ не сильно отличается оригинальностью в плане взаимодействия с DOM, однако я решил обойтись без внешних библиотек. Я предлагаю чуть более умную реализацию поиска. Улучшит это поиск или ухудшит это еще вопрос, тут нужны тонкие настройки. Для поиска плохих слов тут используется анализ Расстояния Левенштейна. Расстояние Левенштейна между словами A и B - если коротко, это колчиество символов которое нужно изменить в слове A чтобы получить слово B. Это дает возможность найти похожие слова. Я не стал приводить тут наивную рекурсивную реализацию этого алгоритма, с ней можно ознакомится по этой ссылке. Для обеспечения работы в словаре откидываются окончания, затем ищутся все элементы, в которых есть слова, расстояние Левенштейна до которых меньше или равно 2(можно слегка улучшить поиск, установив каждому слову свое пороговое расстояние) и далее уже следует замена в DOM. PS. регулярку для выгребания пунктуации тоже нужно усовершенствовать по месту применения. let minDistance = 2; let badword = [ 'Lore', 'non', 'lectu', 'ligula', 'nisi' ]; check(document.querySelector('.content')); function check(el) { if (el.nodeType === Node.TEXT_NODE) { markBadwords(el); } else { [...el.childNodes].forEach(check); } } function markBadwords(el) { let found = el.textContent.split(/,?\s+/).filter(word => { return minDistance >= Math.min.apply(null, badword.map(bad => { return distance(bad, word.toLowerCase()); })); }); if (!found.length) return; let element = document.createElement('span'); let regex = new RegExp(found.join("|"), "gim"); element.innerHTML = el.textContent.replace(regex, w => `${w}`); el.replaceWith(element); } // https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance function distance(a, b) { if (a.length === 0) return b.length; if (b.length === 0) return a.length; var matrix = []; // increment along the first column of each row var i; for (i = 0; i <= b.length; i++) { matrix[i] = [i]; } // increment each column in the first row var j; for (j = 0; j <= a.length; j++) { matrix[0][j] = j; } // Fill in the rest of the matrix for (i = 1; i <= b.length; i++) { for (j = 1; j <= a.length; j++) { if (b.charAt(i-1) == a.charAt(j-1)) { matrix[i][j] = matrix[i-1][j-1]; } else { matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution Math.min(matrix[i][j-1] + 1, // insertion matrix[i-1][j] + 1)); // deletion } } } return matrix[b.length][a.length]; }; .bad { filter: blur(3px) }Lorem ipsum dolor sit amet, consectetur adipiscing elit.Suspendisse non pharetra mauris. Suspendisse a lacinia lacus. Nulla facilisi.Suspendisse eu lectus aliquam, porttitor est eleifend, blandit mauris.Nunc ut bibendum ligul, eu consequat odio. Praesent fermentum nisi a lobortis rhoncus. Phasellus vel metus eu dolor molestie porta.Ответ 4
Если созданные ранее элементы можно пересоздать, то можно сделать очень просто: var div = document.querySelector(".content"); div.innerHTML = div.innerHTML.replace(/(^|.)(Lorem|non|lectus|ligula|nisi)(.|$)/gi, (m, l, w, r) => l.toLowerCase() === l.toUpperCase() && r.toLowerCase() === r.toUpperCase() ? `${l}${w}${r}` : m) .blur { color: red; }Если же элементы пересоздавать нельзя, то стоит воспользоваться чем-то таким: https://ru.stackoverflow.com/a/919286/178988Lorem ipsum dolor sit amet, consectetur adipiscing elit.Suspendisse non pharetra mauris. Suspendisse a lacinia lacus. Nulla facilisi.Suspendisse eu lectus aliquam, porttitor est eleifend, blandit mauris.Nunc ut bibendum ligula, eu consequat odio. Praesent fermentum nisi a lobortis rhoncus. Phasellus vel metus eu dolor molestie porta.<script>alert('non');alert('nonconforming');</script>
Комментариев нет:
Отправить комментарий