Підсвічуємо НЕ ЛАТИНСЬКІ СИМВОЛИ у коді та тексті

2023-Feb-08

У мене навчається багато розробників-початківців. Дуже часто вони запитують "чому не працює, все за вами повторив" і надсилають код. Це питання настільки часто повторюють, що я можу підвести статистику помилок. Однією з найпідступніших помилок є написання не латинських символів у коді. При такій помилкі консоль не показує error і нічого не працює. Після того, як знайдено помилку, студенти просять показати як я її знайшов. Як правило, все просто. Якщо код не працює, помилок у консолі немає, і візуально все повинно працювати, то варто перевірити код на наявність не латинських символів. Дійсно, давайте подивимося на два рядки:

const out = document.querySelector('.out-1');
const out = document.querySelеctor('.оut-1');

Візуально – вони однакові. Насправді, у другому рядку написано два символи на кирилиці. Один це e, другий o. Виявити їх візуально дуже важко. Вихід - або запускати редактор HEX і дивитися код, або використовувати онлайн способи перевірки. Ви скажете, що редактори мають підсвічувати таке? Ні не завжди.

Сьогодні ми напишемо програму на JavaScript, яка дозволить підсвічувати в тексті не латинські символи. Ускладнимо розробку додатковими вимогами:

  • напишемо двома способами: за допомогою рядка та за допомогою регулярних виразів,
  • форматування тексту має зберігатися (не робитимемо кашу),
  • програма повинна перевіряти не тільки тексти, а й код програм,
  • код після перевірки повинен працювати, якщо його скопіюють та вставлять у редактор!

Думаю достатньо. Поїхали. Для початку верстка.

Верстка додатку перевірки не латинських символів

Верстка складається з трьох елементів:

  • textarea з класом або id, куди вводитиметься текст,
  • кнопка, яка запускатиме функцію знаходження не латинських символів,
  • div куди виводитимемо результат тексту.

Зробимо верстку:

<textarea id="latin-text"></textarea>
<button>Search not latin symbols</button>
<div class="latin-result">
  <pre><code></code></pre>
</div>

Зверніть увагу, що для виведення ми будемо використовувати конструкцію pre > code. Це дозволить спростити візуалізацію коду, застосовувати задані у вихідному тексті відступи, перенесення рядків.

Не забувайте створити CSS файл і JavaScript файл і підключити їх обидва. Переходимо до JavaScript.

Пошук не латинських символів за допомогою рядка "дозволених символів"

Для початку в JS файлі поставимо константи, з якими будемо працювати.

const innerText = document.querySelector('#latin-text');
const outerText = document.querySelector('.latin-result');

Тепер повісимо на кнопку події клік, і напишемо функцію, яка отримуватиме з textarea text. Після отримання текст очищається від пробілів на початку і кінці рядка, за допомогою trim. Також додаєм перевірку, якщо текст не ввели (порожній рядок), виходимо з функції.

const innerText = document.querySelector('#latin-text');
const outerText = document.querySelector('.latin-result');

document.querySelector('button').onclick = function() {
     let text = innerText.value.trim();
     if (text ==='') return;
}

Найпростіший варіант знайти символи не латинські - перерахувати дозволені символи та перевіряти за цим патерном. Якщо взяти латинську абетку, то отримаємо:

const template = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz';

усі символи, що не входять сюди, не латинські. Але дозвольте, адже є ще числа, розділові знаки, дужки і так далі. Додамо також їх. Зверніть увагу, що при додаванні лапок доведеться зробити їх екранування, за допомогою \ зворотного слешу. І сам слеш (і прямий і зворотний) теж додаємо. Отримаємо:

const template = '0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz!@#$%^&*()_+-={}[]?.,;:\'\"'

Тепер перейдемо до алгоритму пошуку латинських символів. Для початку ми створимо змінну out = '', куди будемо накопичувати відповідь. Потім циклом опрацюємо вихідний текст. Ось так:

let text = innerText.value.trim();
if (text ==='') return;

const template = '0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz!@#$%^&*()_+-={}[]?.,;:';

let out = '';
for (let i = 0; i <text.length; i++) {

}

Усередині циклу додаємо перевірку. Якщо поточний символ text[i] не входить до рядка template - значить символ не латинський і нам потрібно його виділити. Для простоти виділятимемо тегами <mark></mark>. Перевірку входження організуємо за допомогою indexOf. Якщо символ входить у template, то просто додаємо його в out. Результат - виведемо в outerText.

let text = innerText.value.trim();
if (text ==='') return;

const template = '0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz!@#$%^&*()_+-={}[]?.,;:';

let out = '';
for (let i = 0; i <text.length; i++) {
  if(template.indexOf(text[i]) === -1) {
    out += '<mark>'+text[i]+'</mark>';
  }
  else {
    out += text [i];
  }
  outerText.innerHTML = out;
}

Перевіримо, як працює код на вихідних рядках:

const out = document.querySelector('.out-1');
const out = document.querySelеctor('.оut-1');

Видно що код знайшов два не латинські символи, проте видав дивну проблему в місці, де зліпив вихідні рядки. Спробуємо зберегти переноси рядків, як у вихідному варіанті. Якщо textarea ви робите перенесення рядка, то додаємося символ \n нового рядка. Нам, оскільки ми виводимо на сторінку HTML, необхідно зробити заміну символу перенесення рядка на тег br. Перепишемо умову if так:

for (let i = 0; i <text.length; i++) {
  if (text[i] === 'n') {
    out += '<br>';
  }
  else if(template.indexOf(text[i]) === -1) {
    out += '<mark>'+text[i]+'</mark>';
  }
  else {
    out += text [i];
  }
}

Тестуємо на прикладі і бачимо, що результат виводиться як і очікувалось та зберігає форматування. А символи кирилиці підсвічені правильно. Продовжимо тестування на інших текстах. Напишіть текст суміші латинських символів та будь-яких інших і все буде добре працювати. Проте є проблема. Давайте введемо наступний код для перевірки:

if (a<b) console.log('hi');
<b>Hello</b>

Візуально (див. зображення), все працює. Але якщо вивести в консоль, то побачимо – подвоєння кутових дужок, що явно не є валідним кодом.

Ви можете заперечити, що ми в рядку template не передбачали кутових дужок. Добре. Давайте допишемо <> у template і перевіримо результат на тому ж рядку. А результат виявиться ще гіршим.

З чим пов'язана така поведінка? З тим, що текст з кутовими дужками трактується як HTML-теги, і браузер "відпрацьовує" по них. У разі рекомендується замінити символи "сутностями" , тобто. кодами, які є набором символів, але браузер трактує і виводить як знак, наприклад, кутові дужки. Додаємо визначення сутностей у перевірку:

for (let i = 0; i <text.length; i++) {
  if (text[i] === '>') {
    out += '&#x3E';
  }
  else if (text[i] === '<') {
    out += '<';
  }
  else if (text[i] === 'n') {
    out += '<br>';
  }
  else if(template.indexOf(text[i]) === -1) {
    out += '<mark>'+text[i]+'</mark>';
  }
  else {
    out += text [i];
  }
}

При перевірці – бачимо, що цей код спрацював і коректно вивів кутові дужки та визначає не латиницю. І тоді, фінальний код виглядає так:

const innerText = document.querySelector('#latin-text');
const outerText = document.querySelector('.latin-result');

document.querySelector('button').onclick = function() {
     let text = innerText.value.trim();
     if (text ==='') return;

     const template = '0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz!@#$%^&*()_+-={}[]?.,;:';

     let out = '';
     for (let i = 0; i <text.length; i++) {
         if (text[i] === '>') {
             out += '&#x3E';
         }
         else if (text[i] === '<') {
             out += '<';
         }
         else if (text[i] === 'n') {
             out += '<br>';
         }
         else if(template.indexOf(text[i]) === -1) {
             out += '<mark>'+text[i]+'</mark>';
         }
         else {
             out += text [i];
         }
     }
     console.log(out);
     outerText.innerHTML = out;
}

Залишилося оптимізувати та зачесати код. Але ми залишимо це на читача.

Шукаємо не латинські символи за допомогою регулярних виразів у JavaScript

Якщо у вас у коді програми йде робота з циклами та if операторами, то подумайте – можливо регулярки будуть швидше та простіше? Або простіше візуально (але не завжди швидше). Спробуймо знайти не латинські символи за допомогою регулярних виразів. Як основу застосуємо ту саму функцію:

document.querySelector('button').onclick = function() {
     let text = innerText.value.trim();
     if (text ==='') return;

     const template = '0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz!@#$%^&*()_+-={}[]?.,;:\'\"'

     let out = '';
    
     console.log(out);
     outerText.innerHTML = out;
}

Замінимо константу з перерахуванням усіх дозволених символів за допомогою регулярного виразу. Нам потрібні:

let regexp = /[^\w\s'",\\.:;?!@#$%^&*+-=_{}\[\]()]/gi

де:

  • \w літерний або цифровий символ або знак підкреслення; літери обмежені латиницею
  • \s пробільний символ

а далі ми перераховуємо всі символи, які дозволені: лапки, коми, крапки, розділові знаки і дужки. Перед тими символами, що зарезервовані синтаксисом регулярних виразів, ставимо зворотний слеш для екранування.

Знак ^ на початку регулярного виразу означає, що ми шукаємо все, крім перерахованих символів.

Далі потрібно аналогічно циклу вище, перебрати рядок і якщо символ не входить до перерахованих - замінити. Для такої операції ідеально підходить метод .replace для регулярних виразів. Як він працює? Приймає регулярний вираз і при знайденні збігу застосовує до символу функцію. Тому допишемо такий код:

let result = text.replace(regexp, symbol => '' + symbol + '';

Зробимо такий код і перевіримо результат.

document.querySelector('button').onclick = function() {
  let text = innerText.value.trim();
  if (text ==='') return;

  let regexp = /[^\w\s'",\\.:;?!@#$%^&*+-=_{}\[\]()]/gi

  let result = text.replace(regexp, symbol => '' + symbol + '';

  outerText.innerHTML = result;
  console.log(result); // HTML та CSS
}

Працює. Але, як і в попередньому прикладі, є проблеми з перенесенням рядків і з кутовими дужками. В принципі, можна все зробити в одному replace, однак у нас навчальний урок, тому пишемо умови максимально зрозуміло:

document.querySelector('button').onclick = function() {
	let text = innerText.value.trim();
	if (text ==='') return;

	let regexp = /[^\w\s'",\\.:;?!@#$%^&*+-=_{}\[\]()<>]/gi

	let result = text.replace(/\>/g, str => '>');

	result = result.replace(/\ '<');

	result = result.replace(/\n/g, str => '
'); result = result.replace(regexp, symbol => '' + symbol + ''); outerText.innerHTML = result; console.log(result); // HTML та CSS }

Що ми тут робимо? Спочатку замінюємо кутові дужки сутностями, потім перенесення рядка замінюємо на тег br. Потім - якщо символ не входить до regexp, то обертаємо символ тегом mark. Зверніть увагу, ми виконали заміну кутових дужок сутностями, а потім – додаємо перенесення рядків. Нам потрібно тепер, щоб регулярне вираження не реагувало на дужки. Тому додаємо їх у regexp.

Тепер код зберігає форматування після перевірки, а не латинські (кирилиця) знаки виділяються тегом mark, і візуально помітні. Також, якщо скопіювати перевірений код і вставити в JavaScript, все буде працювати.

Код не є оптимізованим. Можна зменшити кількість застосування методу replace, що прискорить роботу.

Переглянути відео з цього уроку:

Завантажити файл з кодом: Download

Статті

Тотальная інструкція по елементу Select у JavaScript
Кнопка "Показати пароль" на JavaScript + анімація
Задача співбесіди: отримуємо email із рядка