hard

Ваш собственный орфокорректор с автозаменой

Проект недели.

Ранее мы уже делали редактор текста с автозаменой. Тогда исправлением текста занималась нейросеть Яндекса, а мы лишь сделали для этого оболочку. Минус подхода в том, что какой-то механизм решает за вас, какие слова правильные, а какие нужно заменить. Давайте это исправим.

На этой неделе мы делаем орфокорректор, который будет работать по вашим правилам. Вы сами решите, что и как заменять в тексте, и сами пропишете нужные правила. И при необходимости сможете их дополнять.

Это полезно, например, для деловой переписки: менеджер пишет «Засуньте себе в... свои правки, это полная чушь, я не буду это исправлять». А алгоритм заменяет это на «Коллеги! Спасибо за конструктивную критику! Нам потребуется уточнить некоторые моменты, прежде чем мы возьмем эти замечания в работу».

Логика будет такая:

  • берём код прошлого проекта;
  • выкидываем из него всё, что связано с нейронкой;
  • на это место закидываем свой код, который меняет слова и буквы, как нужно нам;
  • проверяем результат.

Регулярные выражения

Чтобы заменить одно слово на другое, в JavaScript используют функцию replace(), в которой пишут сначала, что нужно найти, а затем — на что заменить.

Например, s = s.replace('кто', 'что'); заменит в строке слово «кто» на слово «что». Это удобно и вроде как нам подходит, но смотрите, какие есть минусы у этого подхода:

  • заменяется только первое найденное слово;
  • если «Кто» будет написано с большой буквы, наша функция его уже не найдёт;
  • имя «Гектор» превратится в «Гечтор», а казахстанский город Актобе превратится в «Ачтобе».

Чтобы такой ерунды не происходило, используют регулярные выражения.

Регулярное выражение можно представить как маленький встроенный язык программирования, заточенный под поиск и обработку текста. У этого языка есть специальные команды, которые определяют поведение алгоритма. Эти команды встраиваются в обычный текст, и получаются регулярные выражения. Несколько примеров:

код — найдёт просто эти буквы внутри любого слова, в том числе «нашкодил».


\Bкод\B — найдёт слово «код», окружённое пробелами или в начале предложения. Маркер \b означает границу слова. И тут важное замечание: в JavaScript есть маркер \b, который обозначает границу слова на латинице и \B, который обозначает границу слова в остальных алфавитах. А, например, на Python токен \b работает на всех языках. 


\B[Кк]од\B — найдёт «Код» или «код» как отдельное слово. Буквы внутри квадратных скобок он воспринимает как «или». [Кк] означает «Либо заглавная К, либо строчная к».


\B[Кк]о\W\B — найдёт «Код», «Кот», «Ком», «Кое», и так же со строчной буквы. Токен \w означает любую букву слова на латинице, а \W — на всех языках.


\B[Кк]од\w{0,3}\B — найдёт «Код», «Кода», «Коду», «Кодом», «Кодами» плюс то же самое со строчных букв. В фигурных скобках указывают число возможных повторов предыдущего знака. \w{0,3} означает «любая буква, повторённая от нуля до трёх раз».


[\w-_\.]+@([\w-]+\.)+\w{2,5} — хе-хе-хе. Эта регулярка помогает найти в тексте электронные адреса. Плюсик означает «один или больше», а скобки используются, чтобы прикрепить плюсик к набору символов. Переводится так: «Сколько-то последовательностей из букв, дефисов, подчёркиваний и точек, потом собака, потом сколько-то последовательностей из букв, дефисов и точек, потом от двух до пяти букв. Если хотите, чтобы срабатывали домены типа .apartments, придется ставить в конце десятку.


Обратите внимание: плюс в регулярках означает не «одно сложить с другим», а «то, что написано передо мной, повторить один или много раз».


Если разметка регулярных выражений вам сейчас кажется сложноватой — не печальтесь. Мы сделаем об этом отдельную статью. В реальности там всё очень легко, нужно лишь выучить несколько специальных букв.

А пока что полный список основных команд и свойств регулярных выражений можно посмотреть на Хабре или JavaScript.ru.

Регулярки в JavaScript

Чтобы JavaScript понял, что мы используем регулярные выражения вместо конкретного слова при поиске, используют две косые черты:

/регулярное выражение/флаги

Флаги — это специальные команды для выражений, которые дают им суперспособности: i — нечувствительность к регистру или g — поиск по всей строке.

Вернёмся к случаю с Гектором. Чтобы при замене «кто» на «что» не возникало таких ситуаций, добавим в функцию замены регулярное выражение:

s = s.replace(/\Bкто\B/gi, ' что ');

Вы уже знаете, что \B означает границы слова.

Флаг g означает, что мы пройдём всю строку и найдём все слова «кто», чтобы их заменить. А флаг i делает наш поиск нечувствительным к регистру — он найдёт и «Кто», и «ктО», и даже «КТО».

Теперь, когда мы это знаем, мы можем менять наш текст как угодно:

  • просто менять один текст на другой:

s = s.replace(/\Bкто\B/gi, 'что');

  • менять один символ на другой или на целое словосочетание

s = s.replace(/\Bя\B/gi, 'неизвестно кто');

  • Делать Каждое Слово Большим:

s = s.replace(/\s[а-я]/g, u => u.toUpperCase());

Сразу оговоримся: всё это работает предельно тупо, и, если мы сказали делать замену на «что», то везде будет замена на «что» со строчной. Если мы хотим сохранить заглавные буквы, то замена будет в два шага:

s = s.replace(/\Bкто\B/g, 'что');

s = s.replace(/\BКто\B/g, 'Что');

Теперь у нас есть всё что нужно, чтобы сделать свой редактор с вежливыми автозаменами.

Готовим каркас

Чтобы было быстрее, возьмём старый код и уберём из него всё, что работало с нейрокорректором.

Страница уже готова, в ней только меняем имя скрипта.

<!DOCTYPE html>
<html>
<!-- служебная часть -->
<head>
  <!-- заголовок страницы -->
  <title>Корректор с автозаменой</title>
  <!-- настраиваем служебную информацию для браузеров -->
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style type="text/css">
    /* общие параметры страницы*/
    body {
      text-align: center;
      margin: 10;
      font-family: Verdana, Arial, sans-serif;
      font-size: 16px;
    }
    /* заголовок */
    h1 {
      font-size: 48px;
      text-align: center;
    }
    /* поле ввода */
    .text {
      min-height: 300px;
      min-width: 500px;
      border: solid;
      border-width: 1px;
      text-align: left;
      -webkit-box-shadow: 6px 10px 9px 0px rgba(0, 0, 0, 0.75);
      -moz-box-shadow: 6px 10px 9px 0px rgba(0, 0, 0, 0.75);
      box-shadow: 6px 10px 9px 0px rgba(0, 0, 0, 0.75);
    }
  </style>
  <!-- закрываем служебную часть страницы -->
</head>
<body>
  <!-- подключаем jQuery -->
  <script type="text/javascript" src="
http://yastatic.net/jquery/2.1.3/jquery.min.js
"></script>
  <!-- подключаем наш скрипт с регулярными выражениями -->
  <script type="text/javascript" src="regexp.js"></script>
  <!-- заголовок на странице -->
  <h1>Орфокорректор</h1>
  <!-- пояснительный текст -->
  <p>Напишите что угодно. Регулярные выражения сами всё исправят.</p>
  <!-- поле ввода текста -->
  <textarea id="text_field" class="text"></textarea>
</body>
<!-- конец всей страницы -->
</html>

Почистили скрипт:

// обработчик нажатия на клавиши
document.addEventListener('keydown', function (e) {
  // если нажат пробел или энтер
  if ((e.keyCode == 32) || (e.keyCode == 13)) {
    // тут будет наш код
  }
});

Настраиваем правила автозамены в скрипте

Первое, что нам понадобится, — переменная, чтобы хранить текст из поля ввода, который мы будем обрабатывать. Её мы поставим в самое начало скрипта:

// строковая переменная, чтобы хранить текст из поля ввода var s = '';

Теперь в обработчик нажатия вместо нейроскрипта помещаем наш код. Принцип такой: берём переменную, применяем на неё одно правило, переходим к следующему. При каждом нажатии пробела или энтера наш код пройдёт по всем правилам и заменит нужные слова и символы:

// запоминаем содержимое поля ввода текста в отдельной переменной
s = $('#text_field').val();
// блок автозамены, первое слово — что ищем, второе — на что заменяем
s = s.replace(/\Bкое кто\B/gi, 'кое-кто');
s = s.replace(/\Bкое что\B/gi, 'кое-что');
s = s.replace(/\Bв течени\W\B/gi, 'в течение');
s = s.replace(/\Bвообщем\W\B/gi, 'в общем');
s = s.replace(/\Bвобще\W\B/gi, 'вообще');
s = s.replace(/\Bдичайше\B/gi, 'в высшей степени');
s = s.replace(/\Bоху\Wть\B/gi, 'Надо же');
s = s.replace(/\Bоху\Wть\B/gi, 'подозрительно');
s = s.replace(/\Bя\sе\W+л\sтак\W+(работу|задачи)\B/gi, 'подобная постановка вопроса в высшей степени возмутительна');
s = s.replace(/\Bоху\Wл\W?\b/gi, 'позволяет себе лишнего');
s = s.replace(/\Bоху\Wвший\B/gi, 'недопустимый');
s = s.replace(/\Bоху\Wвшая\B/gi, 'вызывающая опасения');
s = s.replace(/\B[иИ]ди(те)?\s(на|в)\s?\W{3,5}\B/gi, 'заранее спасибо');
s = s.replace(/\Bп[адир]{4}с\W?/gi, 'уважаемые коллеги');
s = s.replace(/\BТЗ\B/gi, 'техническое задание');
s = s.replace(/\Bчушь\sполная\B/gi, 'недостоверные сведения');
s = s.replace(/\Bнужно\sвчера\B/gi, 'это срочно');
s = s.replace(/\Bполная\sж[аоп]{3}\B/gi, 'присутствуют существенные проблемы');
// отправляем отредактированные строки назад в поле ввода
$('#text_field').val(s);

// строковая переменная, чтобы хранить текст из поля ввода
var s = '';
// обработчик нажатия на клавиши
document.addEventListener('keydown', function (e) {
  // если нажат пробел или энтер
  if ((e.keyCode == 32) || (e.keyCode == 13)) {
    // запоминаем содержимое поля ввода текста в отдельной переменной
    s = $('#text_field').val();
    // блок автозамены, первое слово — что ищем, второе — на что заменяем
    s = s.replace(/\Bкое кто\B/gi, 'кое-кто');
    s = s.replace(/\Bкое что\B/gi, 'кое-что');
    s = s.replace(/\Bв течени\W\B/gi, 'в течение');
    s = s.replace(/\Bвообщем\W\B/gi, 'в общем');
    s = s.replace(/\Bвобще\W\B/gi, 'вообще');
    s = s.replace(/\Bдичайше\B/gi, 'в высшей степени');
    s = s.replace(/\Bоху\Wть\B/gi, 'Надо же');
    s = s.replace(/\Bоху\Wть\B/gi, 'подозрительно');
    s = s.replace(/\Bя\sе\W+л\sтак\W+(работу|задачи)\B/gi, 'подобная постановка вопроса в высшей степени возмутительна');
    s = s.replace(/\Bоху\Wл\W?\b/gi, 'позволяет себе лишнего');
    s = s.replace(/\Bоху\Wвший\B/gi, 'недопустимый');
    s = s.replace(/\Bоху\Wвшая\B/gi, 'вызывающая опасения');
    s = s.replace(/\B[иИ]ди(те)?\s(на|в)\s?\W{3,5}\B/gi, 'заранее спасибо');
    s = s.replace(/\Bп[адир]{4}с\W?/gi, 'уважаемые коллеги');
    s = s.replace(/\BТЗ\B/gi, 'техническое задание');
    s = s.replace(/\Bчушь\sполная\B/gi, 'недостоверные сведения');
    s = s.replace(/\Bнужно\sвчера\B/gi, 'это срочно');
    s = s.replace(/\Bполная\sж[аоп]{3}\B/gi, 'присутствуют существенные проблемы');
    // отправляем отредактированные строки назад в поле ввода
    $('#text_field').val(s);
  }
});

Результат работы скрипта.

Что дальше

Возможности регулярок практически безграничны. На них работают автокорректоры, автотипографы и все те алгоритмы, которым нужно механически обработать текст по правилам.

Сейчас наш алгоритм очень простой: он не учитывает рода и числа, не понимает значений слов, а текст после него лучше перечитать, чтобы не слетели склонения и спряжения. Но всё это решаемо — достаточно просто написать много новых правил.

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Начать карьеру в ИТ
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Еще по теме
hard