Проверяем текст в Главреде

Проверяем текст в Главреде

Добавляем новые возможности через API.

Мы уже рассказывали, что такое API. Теперь используем эту технологию в наших проектах: возьмём ранее сделанный текстовый редактор с нейроорфокорректором и научим его проверять текст в сервисе «Главред» — у него открытый API, который несложно прикрутить.

Логика работы

Как и большинство программистов, мы будем использовать предыдущие наработки и готовые решения — так проще и быстрее. Для проекта нам понадобится:

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

То же самое сделаем и для Главреда — отдельную кнопку, которая отправит наш текст на проверку в сервис.

Чтобы не усложнять проект, разобъём его на две части: сегодня сделаем только подсветку в Главреде, а во второй части добавим оформление, чтобы выглядело красиво и наглядно.

Переделываем страницу

Для работы нам понадобится исходный код текстового редактора с нейронкой. Для начала изменим код на 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="script.js"></script>
  <!-- заголовок на странице -->
  <h1>Орфокорректор</h1>
  <!-- пояснительный текст -->
  <p>Напишите что угодно с ошибками. Страница сама всё исправит.</p>
  <!-- поле ввода текста -->
  <textarea id="text_field" class="text"></textarea>
</body>
<!-- конец всей страницы -->

</html>

Так как нам нужно будет выделять слова, как в Главреде, то заменим <textarea>, который не умеет форматировать текст, на <div contenteditable="true">, который форматировать умеет. Из-за этого у нас поменяется внешний вид поля ввода — он растянется на всю ширину экрана — но это мы исправим в другой раз.

Чтобы выделять слова, которые нам пришлёт Главред, мы будем использовать тег <span> — в него удобно заворачивать отдельные элементы текста и помечать их любым образом. Сегодня выделим их простой светло-голубой подсветкой, а позднее части заменим на красивое подчёркивание. Для этого добавим описание нового стиля в соответствующий раздел на странице:

/* слова, которые нашёл Главред */
span{
     background-color: lightblue;
}

Для того чтобы использовать API Главреда, его нужно подключить. В нашем случае это скрипт на JavaScript, поэтому подключаем его на странице, как и любой другой внешний скрипт:

<!-- подключаем API Главреда -->
  <script type="text/javascript"
src="https://api.glvrd.ru/v1/glvrd.js"></script>

Последнее, что нам осталось сделать, — добавить кнопки управления. Их будет три: исправление орфографии, отправка в Главред и отмена обработки Главредом. Последняя кнопка нужна для того, чтобы отменить текущую проверку, если обработка идёт слишком долго или вам проверка уже оказалась не нужна:

<!-- добавляем отступ -->
  <br>
  <!-- добавляем кнопки на страницу -->
  <button id="spell_b" onclick="spell()">Исправить</button>
  <button id="glvrd_b" onclick="glvrdz()">Главред</button>
  <button id="abort_b"
onclick="abort_glvrdz()" disabled>Отменить проверку Главреда</button>

<!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);
    }
    /* слова, которые нашёл Главред */
    span {
      background-color: lightblue;
    }
  </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="script.js"></script>
  <!-- подключаем API Главреда -->
  <script type="text/javascript" src="https://api.glvrd.ru/v1/glvrd.js"></script>
  <!-- заголовок на странице -->
  <h1>Орфокорректор</h1>
  <!-- пояснительный текст -->
  <p>Напишите что угодно с ошибками. Страница сама всё исправит.</p>
  <!-- поле ввода текста -->
  <div id="text_field" contenteditable="true" class="text"></div>
  <!-- добавляем отступ -->
  <br>
  <!-- добавляем кнопки на страницу -->
  <button id="spell_b" onclick="spell()">Исправить</button>
  <button id="glvrd_b" onclick="glvrdz()">Главред</button>
  <button id="abort_b" onclick="abort_glvrdz()" disabled>Отменить проверку Главреда</button>
</body>
<!-- конец всей страницы -->
</html>

По нажатию на кнопки будут вызываться соответствующие функции, которых у нас пока нет. Чтобы это исправить, все изменения будем вносить в .js-файл из оригинального проекта:

$(document).ready(function () {
  // эта функция получает наш результат проверки орфографии
  fix_spell = function (data) {
    data.forEach(function (elem) {
      // она находит наше поле ввода по имени
      $('#text_field').val(
        // и меняет всё на правильные слова без ошибок
        $('#text_field').val().replace(
          elem['word'],
          elem['s'][0] || elem['word']
        )
      );
    });
  }
});
// обработчик нажатия на клавиши
document.addEventListener('keydown', function (e) {
  // если нажат пробел или энтер
  if ((e.keyCode == 32) || (e.keyCode == 13)) {
    // делим текст на строки
    var lines = $('#text_field').val().replace(/\r\n|\n\r|\n|\r/g, "\n").split("\n");
    // и обрабатываем каждую строчку:
    lines.forEach(function (line) {
      if (line.length) {
        // отправляем строку со словами на проверку в Спеллер, результат сразу отправляется в функцию fix_spell
        $.getScript('http://speller.yandex.net/services/spellservice.json/checkText?text=' + line + '&callback=fix_spell');
      }
    });
  }
});

Проверка орфографии по кнопке

Нам больше не нужно обрабатывать каждое нажатие пробела или энтера, поэтому нам больше не нужен обработчик событий по нажатию. Вместо этого нам нужно завести новую функцию spell() — именно она она вызывается по нажатию на кнопку «Исправить».

Чтобы не было такого, что пока текст на проверке, мы отправляем его в Главред, давайте отключать обе эти кнопки на время проверки орфографии. Как только проверка закончится — кнопки снова будут работать. Это поможет нам избежать ситуации, когда на проверку отправятся разные версии текста.

Раньше мы обращались к тексту, написанному пользователем, через вот такое заклинание:

$('#text_field').val()

Это работало, потому что человек писал в поле вида Textarea, у которого есть значение value, в котором и хранился текст.

Теперь у нас вместо текстовой области (textarea) просто блок на странице — тег <div>. Пользователь будет писать в него, а мы будем доставать из него написанное. Для этого нужно такое заклинание:

document.getElementById('text_field').innerHTML 

Теперь заходим в уже написанную нами функцию fix_spell() и меняем обращение к тексту.

Вот что у нас получится после всех исправлений:

// эта функция исправляет в тексте слова с ошибками
function fix_spell(data) {
  data.forEach(function (elem) {
    // берём наш текст и последовательно заменяем все слова с ошибками на правильные
    document.getElementById('text_field').innerHTML = document.getElementById('text_field').innerHTML.replace(
      elem['word'],
      elem['s'][0] || elem['word']
    );
  });
}
// обрабатываем нажатие на кнопку «Исправить»
function spell() {
  // делаем кнопки недоступными для нажатия
  document.getElementById('glvrd_b').disabled = true;
  document.getElementById('spell_b').disabled = true;
  // получаем текст из окна редактора
  var lines = document.getElementById('text_field').innerHTML.replace(/\r\n|\n\r|\n|\r/g, "\n").split("\n");
  // и обрабатываем его
  lines.forEach(function (line) {
    // если есть хоть один символ —
    if (line.length) {
      // отправляем строку со словами на проверку в Спеллер, результат сразу отправляется в функцию fix_spell
      $.getScript('http://speller.yandex.net/services/spellservice.json/checkText?text=' + line + '&callback=fix_spell');
    }
  });
  // снова делаем кнопки доступными для нажатия
  document.getElementById('spell_b').disabled = false;
  document.getElementById('glvrd_b').disabled = false;
}

API Главреда — подготовка

На официальной странице API проекта можно найти готовый пример — возьмём его за основу и немного переделаем под наши задачи.

Сначала добавим блок проверки доступности сервиса — если Главред недоступен, мы сразу получим сообщение об этом. Просто скопируем из примера и вставим в начало нашего .js-файла:

// блок проверки работоспособности сервера Главреда
if(!glvrd) {
alert('Главред не загружен');
} else {
glvrd.getStatus(function(result) {
if(result.status == 'ok') {
InitButtons();
} else {
alert('Главред выключен: ' + result.message);
}
})
}

Там же находим команду отмены проверки и вставляем его в функцию abort_glvrdz(), которая вызывается при нажатии на кнопку «Отменить проверку Главреда»:

// отмена проверки на сервере
function abort_glvrdz(){
     glvrd.abortProofreading(); }

API Главреда — отправка и расшифровка результатов

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

  • мы должны сначала отправить наш текст на сервер с помощью метода glvrd.proofread();
  • в ответ мы получим объект result, в котором будет всё, что сервис думает по поводу нашего текста;
  • среди содержимого result есть свойство fragments — это массив, в котором содержатся найденные слова, которые нам нужно выделить;
  • у каждого элемента такого массива есть свои свойства: начало стоп-слова (с какого символа по порядку оно начинается), конец и подсказка. Подсказка нам пока не нужна, а вот начало и конец пригодятся.

Получается, что нам известны координаты начала и конца найденных слов, на которые сервис обратил внимание, а значит, мы можем в этих местах поставить тег <span>..</span>. Мы в этот тег завернём все найденные стоп-слова, а так как мы всё делаем на странице в элементе <div>, то браузер сам выделит эти слова так, как написано у нас в стилях.

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

Здесь есть один тонкий момент: когда мы добавим в строку наши теги <span>..</span>, то у нас изменится длина строки и собьются все координаты остальных стоп-слов. Чтобы этого не произошло, мы добавим переменную смещения — в ней будем хранить то количество символов, на сколько мы увеличили длину строки. Например, когда мы добавим тег <span>, то этим мы увеличим длину строки на 6 символов, а когда закрывающий тег — на 7 символов.

Чтобы при каждой отправке мы получали новый результат, на который не влияет подсветка старых слов, мы будем очищать наш текст от тега<span>..</span> при каждой отправке. Это позволит нам каждый раз получать новую и правильную разметку текста.

С кнопками на странице поступаем так же, как и при проверке на ошибки — на время проверки делаем их неактивными, а кнопку отмены проверки в Главреде, наоборот, активной.

// тут будем хранить количество символов, которое мы добавляем в строку нашими тегами <span>
var rep = 0;
// переменные для обработки строк
var str = '';
var temp_s = '';
var temp_s_2 = '';
// подсвечиваем слова, которые нашёл Главред
function mark(s, st, en) {
  // разбиваем текст по переменным на до и после начала очередного слова из Главреда
  temp_s = s.substring(0, st + rep);
  temp_s_2 = s.substring(st + rep, s.lenght);
  // так как добавляем <span>, то увеличиваем общую длину строки на 6 символов
  rep += 6;
  // добавляем открывающий тег и соединяем всё в одну строку
  s = temp_s + '<span>' + temp_s_2;
  // делаем то же самое, чтобы добаавить закрываюший тег </span> после нужного слова
  temp_s = s.substring(0, en + rep);
  temp_s_2 = s.substring(en + rep, s.lenght);
  s = temp_s + '</span>' + temp_s_2;
  rep += 7;
  // результат помещаем в исходную строку для следующей итерации
  str = s;
}
// отправка текста в Главред и обработка результатов
function glvrdz() {
  // запрещаем все кнопки, кроме остановки проверки в Главреде
  document.getElementById('spell_b').disabled = true;
  document.getElementById('glvrd_b').disabled = true;
  document.getElementById('abort_b').disabled = false;
  // получаем текст из окна редактора
  var text = document.getElementById('text_field').innerHTML;
  // убираем теги разметки, которые мы сами добавили
  text = text.replace(/\B<span>\B/gi, '');
  text = text.replace(/\B<\/span>\B/gi, '');
  // помещаем очищенный текст назад в окно редактора
  document.getElementById('text_field').innerHTML = text;
  // обнуляем переменную сдвига количества символов
  rep = 0;
  // используем API — отправляем текст на проверку на сервер Главреда
  glvrd.proofread(text, function (result) {
    // если всё в порядке
    if (result.status == 'ok') {
      // получаем содержимое окна редактора
      str = document.getElementById('text_field').innerHTML;
      // цикл выполнится столько раз, сколько слов нужно пометить
      for (var i = 0; i < result.fragments.length; i++) {
        // помечаем каждое найденное слово, зная его начало и конец
        mark(str, result.fragments[i].start, result.fragments[i].end);// }
      }
      // выводим результат в окно редактора
      document.getElementById('text_field').innerHTML = str;
      // если сервер не отвечает — выведем ошибку
    } else {
      alert(result.message);
    }
  });
  // снова делаем все кнопки доступными для нажатия, кроме кнопки отмены
  document.getElementById('spell_b').disabled = false;
  document.getElementById('glvrd_b').disabled = false;
  document.getElementById('abort_b').disabled = true;
}

// блок проверки работоспособности сервера Главреда
if (!glvrd) {
  alert('Главред не загружен');
} else {
  glvrd.getStatus(function (result) {
    if (result.status == 'ok') {
      InitButtons();
    } else {
      alert('Главред выключен: ' + result.message);
    }
  })
}
// эта функция исправляет в тексте слова с ошибками
function fix_spell(data) {
  data.forEach(function (elem) {
    // берём наш текст и последовательно заменяем все слова с ошибками на правильные
    document.getElementById('text_field').innerHTML = document.getElementById('text_field').innerHTML.replace(
      elem['word'],
      elem['s'][0] || elem['word']
    );
  });
}
// обрабатываем нажатие на кнопку «Исправить»
function spell() {
  // делаем кнопки недоступными для нажатия
  document.getElementById('glvrd_b').disabled = true;
  document.getElementById('spell_b').disabled = true;
  // получаем текст из окна редактора
  var lines = document.getElementById('text_field').innerHTML.replace(/\r\n|\n\r|\n|\r/g, "\n").split("\n");
  // и обрабатываем его
  lines.forEach(function (line) {
    // если есть хоть один символ —
    if (line.length) {
      // отправляем строку со словами на проверку в Спеллер, результат сразу отправляется в функцию fix_spell
      $.getScript('http://speller.yandex.net/services/spellservice.json/checkText?text=' + line + '&callback=fix_spell');
    }
  });
  // снова делаем кнопки доступными для нажатия
  document.getElementById('spell_b').disabled = false;
  document.getElementById('glvrd_b').disabled = false;
}
// тут будем хранить количество символов, которое мы добавляем в строку нашими тегами <span>
var rep = 0;
// переменные для обработки строк
var str = '';
var temp_s = '';
var temp_s_2 = '';
// подсвечиваем слова, которые нашёл Главред
function mark(s, st, en) {
  // разбиваем текст по переменным на до и после начала очередного слова из Главреда
  temp_s = s.substring(0, st + rep);
  temp_s_2 = s.substring(st + rep, s.lenght);
  // так как добавляем <span>, то увеличиваем общую длину строки на 6 символов
  rep += 6;
  // добавляем открывающий тег и соединяем всё в одну строку
  s = temp_s + '<span>' + temp_s_2;
  // делаем то же самое, чтобы добаавить закрываюший тег </span> после нужного слова
  temp_s = s.substring(0, en + rep);
  temp_s_2 = s.substring(en + rep, s.lenght);
  s = temp_s + '</span>' + temp_s_2;
  rep += 7;
  // результат помещаем в исходную строку для следующей итерации
  str = s;
}
// отправка текста в Главред и обработка результатов
function glvrdz() {
  // запрещаем все кнопки, кроме остановки проверки в Главреде
  document.getElementById('spell_b').disabled = true;
  document.getElementById('glvrd_b').disabled = true;
  document.getElementById('abort_b').disabled = false;
  // получаем текст из окна редактора
  var text = document.getElementById('text_field').innerHTML;
  // убираем теги разметки, которые мы сами добавили
  text = text.replace(/\B<span>\B/gi, '');
  text = text.replace(/\B<\/span>\B/gi, '');
  // помещаем очищенный текст назад в окно редактора
  document.getElementById('text_field').innerHTML = text;
  // обнуляем переменную сдвига количества символов
  rep = 0;
  // используем API — отправляем текст на проверку на сервер Главреда
  glvrd.proofread(text, function (result) {
    // если всё в порядке
    if (result.status == 'ok') {
      // получаем содержимое окна редактора
      str = document.getElementById('text_field').innerHTML;
      // цикл выполнится столько раз, сколько слов нужно пометить
      for (var i = 0; i < result.fragments.length; i++) {
        // помечаем каждое найденное слово, зная его начало и конец
        mark(str, result.fragments[i].start, result.fragments[i].end);// }
      }
      // выводим результат в окно редактора
      document.getElementById('text_field').innerHTML = str;
      // если сервер не отвечает — выведем ошибку
    } else {
      alert(result.message);
    }
  });
  // снова делаем все кнопки доступными для нажатия, кроме кнопки отмены
  document.getElementById('spell_b').disabled = false;
  document.getElementById('glvrd_b').disabled = false;
  document.getElementById('abort_b').disabled = true;
}
// отмена проверки на сервере
function abort_glvrdz() {
  glvrd.abortProofreading();
}

Попробуйте теперь собрать этот проект самому или протестируйте его на нашем сервере. Сравним результат с оригиналом:

Вот так выглядит текст, который проверил настоящий Главред. Кроме стоп-слов он проверяет читаемость и удобство чтения.
А вот, что мы получили с API. Отличие в том, что на момент написания этой статьи доступен только старый API, где есть только проверка на стоп-слова.

Что дальше

Наш пример хоть и показывает, как можно использовать API для своих проектов, но выглядит при этом очень неказисто:

  • плохие отступы от краёв экрана
  • подсветка текста вместо подчёркивания
  • нет подсказок
  • нет ссылок на правила хорошего текста
  • нет оценки

Всё это мы исправим в следующей итерации, а пока попробуйте сделать это сами.

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

Чтобы программа не падала из-за разных ошибок

easy
Автоматическое оглавление на странице
Автоматическое оглавление на странице

Поручите это машине.

easy
Что означает ошибка TypeError: 'list' object cannot be interpreted as an integer
Что означает ошибка TypeError: 'list' object cannot be interpreted as an integer

Неочевидная ошибка при организации цикла в Python.

easy
Своя игра: создаём собственную «Змейку»

Работы на 10 минут, а удовольствия на целый день.

medium
Непобедимый пинг-понг на JavaScript
Непобедимый пинг-понг на JavaScript

Попробуйте продержаться как можно дольше.

easy
Тестируем и исправляем калькулятор на JavaScript
Тестируем и исправляем калькулятор на JavaScript

В нём много ошибок, но мы их пофиксим

medium
Как включить CSP на сервере для защиты сайта
Как защитить сайт от хулиганских скриптов

Приключение на 5 минут.

easy
Красивые ссылки на вашем сайте

Меняем стандартное подчёркивание на дизайнерское.

medium
Пишем свой генератор паролей
Пишем свой генератор паролей

Готовый код с уникальным алгоритмом шифрования. Возьми и сделай.

medium
Что означает ошибка TypeError: 'str' object does not support item assignment
Что означает ошибка TypeError: 'str' object does not support item assignment

Ошибка из другого языка

easy
medium