Пишем "Добрый браузер правды" или замена слов на загружаемых веб-страницах
Первым массово известным продуктом такого рода был "добрый браузер правды" от Навального и Ко, так что это не какая-то свежая аналогия :) Суть дела, думаю, понятна - как сделать так, чтобы на всех загружаемых страницах одни слова могли заменяться на другие в соответствии с неким словарём или списком. При этом хотелось бы, чтоб замены были достаточно гибкими, например, умели учитывать падежные конструкции русского языка и РеГиСтР символов.
Попробуем разработать такое приложение, а точней, расширение для "Хрома", "с нуля".
Файл манифеста manifest.json
будет совсем типовым:
{ "manifest_version": 2, "name": "Добрый браузер правды", "version": "0.1", "icons": { "16": "icon32.png", "48": "icon128.png", "128": "icon128.png" }, "description": "Заменяет запрещённые слова на их синонимы", "content_scripts": [ { "matches": ["*://*/*"], "js": ["script.js"], "run_at": "document_end" } ] }
В нём дополнительно указано, что замены делаются до конца документа (document_end
). Кодировка этого и остальных файлов - Юникод (UTF-8).
Картинки icon32.png
и icon128.png
соответствующих размеров я нарисовал в Paint, приводить их здесь незачем, а главный файл script.js
мы сейчас напишем.
Функцию для рекурсивного обхода всех узлов документа я банально скоммуниздил вот отсюда:
function walk(node) { var child, next; if (node.nodeType) switch (node.nodeType) { case 1: // Element case 9: // Document case 11: // Document fragment child = node.firstChild; while ( child ) { next = child.nextSibling; walk(child); child = next; } break; case 3: // Text node replaceMe(node); break; } }
Достаточно единственного глобального вызова этой функции, чтобы обход заработал:
walk(document.body);
Будет не так скучно, если некоторые замены мы сделаем вариативными, то есть, слово для замены будет случайно выбираться из списка вариантов. Поможет в этом маленькая функция randomWord
:
function randomWord(words) { return words[Math.floor(Math.random()*words.length)]; }
Осталось написать функцию replaceMe
, которая выполнит всю работу. Она будет устроена так:
function replaceMe(textNode) { var text = textNode.nodeValue; //Нужные замены в тексте text textNode.nodeValue = text; }
Сами замены сделаем с помощью метода Javascript replace, который "понимает" не только тупую замену слова на слово, но может работать и с регулярными выражениями, обеспечивающими должную гибкость поиска словоформ.
Если Вы знакомы с RegExp'ами, то будет совсем легко, если нет - помогут показанные ниже простые примеры.
1. Просто меняем слово на слово, второе слово может иметь несколько вариантов
text = text.replace(/Путин/g, randomWord(["Обещалкин","Поребриков"]));
Здесь слова для замены склоняются так же, как искомое, учёт падежей излишен. Ключ
/g
после строки поиска нужно указывать обязательно, если требуются замены всех вхождений.
Если к ключу добавить ещё символ i
- будет игнорироваться регистр символов при поиске (/gi
)
2. Замена целых словосочетаний
text = text.replace(/(Един)(ая|ой)(\s)(Росси)(я|и)/g, "Пархат$2$3Парти$5"); // $1 $2 $3 $4 $5
$1, ..., $5
- номера вхождений подшаблонов в круглых скобках, эти подшаблоны можно ставить потом в строку для замены. Мы берём из исходной строки окончание первого слова $2
, пробельные разделители $3
и окончание второго слова $5
, так что получается всё корректно;
(ая|ой)
- альтернатива; конструкцию (...|...)
можно вкладывать произвольное количество раз. В ней легко учесть отличающиеся части слов;
(\s)
- пробельные символы.
3. Применяем подшаблоны и замену по списку одновременно
text = text.replace(/(Медведев)(|а|е|у)/g, randomWord(["Айфончик$2","Нанопрезик$2"]));
Обратите внимание, что второй подшаблон может быть и пустым - (|а|е|у)
, это обеспечит нормальную замену для именительного падежа.
Нужно следить, если какие-то падежные окончания у слов для замены не совпадают с исходным, такие варианты можно обработать отдельно:
text = text.replace(/Медведевым/g, randomWord(["Айфончиком","Нанопрезиком"]));
4. Замена только в начале слова
text = text.replace(/(\s|^)(думск)(ий|ого|им|ом)/gi, "$1Шпанск$3");
^
- контроль начала слова. Слово "думский" будет обработано, а, к примеру, фамилия "Стародумский" - нет.
Здесь первая буква "Шпанского комитета" принудительно станет большой.
Если хотим, чтоб замены оставляли регистр первой буквы искомого слова "как есть", придётся часть выражений дублировать:
text = text.replace(/(\s|^)(Думск)(ий|ого|им|ом)/g, "$1Шпанск$3"); text = text.replace(/(\s|^)(думск)(ий|ого|им|ом)/g, "$1шпанск$3");
Это относится и к остальным пунктам.
Стоит сказать, что конструкция \b
, использующаяся для обозначения границы слова, не работает с кириллицей (или работает как-то криво), хотя границу слова, написанного латинскими буквами, определяет.
Может пригодиться также спецсимвол $
, обозначающий конец слова.
Пополнив списки по этим несложным шаблонам, вы сможете сделать собственный комфортный для вас лексикон своему браузеру :)
А я упакую папку с исходниками расширения в ZIP и прикреплю его к статье, о подключении сторонних расширений к "Хрому" говорится здесь,
о дурацком предупреждении на сторонние расширения - тут. После установки расширения проверить его работу можно, например, на этой странице.
Скачать архив .zip с этим расширением Chrome (15 Кб)
P.S. из 2021-го. К сожалению, в актуальных версиях Firefox скачанное с этой страницы и установленное из файла расширение непосредственно работать уже не будет из-за изменения требований к служебным файлам. Хотя, наверное, несложно было бы модифицировать.
Проверил также удалённо в "Хромом" актуальной версии 88 - всё работает (скачать архив, распаковать без создания папки любым архиватором, в браузере обратиться Настройки - Расширения, разрешить "режим разработчика", потом "Загрузить распакованное расширение", показав на папку).
16.11.2015, 15:02 [7071 просмотр]