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

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

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

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

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

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

Что­бы заме­нить одно сло­во на дру­гое, в 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);
ГОТОВЫЙ СКРИПТ regexp.js

// строковая переменная, чтобы хранить текст из поля ввода
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);
  }
});

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

Что дальше

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

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