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

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

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

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

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

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

Что­бы заме­нить одно сло­во на дру­гое, в 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, 'Что');

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

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

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

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

Каркас

    
language: HTML
<!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>


Ско­пи­ро­вать код
Код ско­пи­ро­ван

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

    
language: JavaScript
// обработчик нажатия на клавиши

document.addEventListener('keydown', function(e) {

 

  // если нажат пробел или энтер

  if((e.keyCode == 32) || (e.keyCode == 13) ){

 

    // тут будет наш код

  }

});


Ско­пи­ро­вать код
Код ско­пи­ро­ван

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

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

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

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

    
language: JavaScript
// запоминаем содержимое поля ввода текста в отдельной переменной

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

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);

  }

});


Ско­пи­ро­вать код
Код ско­пи­ро­ван

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

Что дальше

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

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