16 липня 2023 р.

Альтернація (АБО) |

Альтернація – це термін у регулярному виразі, який насправді є простим “АБО”.

Вона позначається символом вертикальної лінії |.

Наприклад, нам треба знайти мови програмування: HTML, PHP, Java або JavaScript.

Відповідний регулярний вираз: html|php|java(script)?.

Приклад використання:

let regexp = /html|php|css|java(script)?/gi;

let str = "Першим з’явився HTML, потім CSS, далі JavaScript";

alert( str.match(regexp) ); // 'HTML', 'CSS', 'JavaScript'

Ми вже бачили подібне – квадратні дужки. Вони дозволяють обирати між декількома символами, наприклад gr[ae]y знайде gray або grey.

Квадратні дужки дозволяють працювати тільки з символами, або наборами символів. Натомість Альтернація працює з будь-якими виразами. Регулярний вираз A|B|C означає пошук одного з символів: A, B або C.

Наприклад:

  • gr(a|e)y означає те саме, що й gr[ae]y.
  • gra|ey означає gra або ey.

Для того, щоб використати альтернацію з обраною частиною шаблону, ми можемо загорнути його у дужки:

  • Я люблю HTML|CSS знайде Я люблю HTML або CSS.
  • Я люблю (HTML|CSS) знайде Я люблю HTML або Я люблю CSS.

Приклад: регулярний вираз для часу

У попередніх статтях було завдання написати регулярний вираз для пошуку часу у форматі гг:хх, наприклад 12:00. Однак простий шаблон \d\d:\d\d недостатньо точний. Він приймає 25:99 як час (99 хвилин підходять до шаблону, однак цей час не є вірним).

Як ми можемо написати кращий шаблон?

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

  • Якщо перша цифра 0 або 1, тоді наступна може бути будь-якою цифрою: [01]\d.
  • В іншому випадку, якщо перша цифра 2, тоді наступна має бути від 0 до 3 [0-3].
  • (іншої першої цифри бути не може)

Ми можемо написати обидва варіанти у регулярному виразі за допомогою альтернації: [01]\d|2[0-3].

Далі, хвилини мають бути від 00 до 59. Мовою регулярних виразів це може бути написано таким чином [0-5]\d: перша цифра 0-5, а за нею будь-яка.

Якщо ми зберемо шаблони годин та хвилин докупи, то вийде ось так: [01]\d|2[0-3]:[0-5]\d.

Майже готово, однак тут є проблема. Альтернація | зараз відбувається між [01]\d та 2[0-3]:[0-5]\d.

Тобто: хвилини додалися до другого варіанту альтернації, більш наочна картинка:

[01]\d  |  2[0-3]:[0-5]\d

Такий шаблон буде шукати [01]\d або 2[0-3]:[0-5]\d.

Але це невірно. Нам необхідно, щоб альтернація використовувалась тільки у частині регулярного виразу, який відноситься до “годин”, щоб дозволити [01]\d АБО 2[0-3]. Виправимо це, огорнувши “години” у дужки: ([01]\d|2[0-3]):[0-5]\d.

Остаточне рішення:

let regexp = /([01]\d|2[0-3]):[0-5]\d/g;

alert("00:00 10:10 23:59 25:99 1:2".match(regexp)); // 00:00,10:10,23:59

Завдання

Існує багато мов програмування, наприклад Java, JavaScript, PHP, C, C++.

Напишіть регулярний вираз, який знаходить вищезгадані мови у рядку Java JavaScript PHP C++ C:

let regexp = /your regexp/g;

alert("Java JavaScript PHP C++ C".match(regexp)); // Java JavaScript PHP C++ C

Перше, що може спасти на думку – перерахувати мови, розділивши їх за допомогою |.

Однак, це не спрацює належним чином:

let regexp = /Java|JavaScript|PHP|C|C\+\+/g;

let str = "Java, JavaScript, PHP, C, C++";

alert( str.match(regexp) ); // Java,Java,PHP,C,C

Механізм регулярних виразів шукає альтернації одну за одною. Тобто, спочатку він перевіряє, чи маємо ми Java, якщо немає – шукає JavaScript і так далі.

У результаті, JavaScript ніколи не буде знайден, лише тому що Java перевіряється першою.

Так само і з мовами C та C++.

Існує два розв’язання цієї проблеми:

  1. Змінити порядок, щоб спочатку перевірялись довші співпадіння: JavaScript|Java|C\+\+|C|PHP.
  2. З’єднати варіанти, які починаються однаково: Java(Script)?|C(\+\+)?|PHP.

У дії:

let regexp = /Java(Script)?|C(\+\+)?|PHP/g;

let str = "Java, JavaScript, PHP, C, C++";

alert( str.match(regexp) ); // Java,JavaScript,PHP,C,C++

ВВ-тег має вигляд [tag]...[/tag], де tag – це один з: b, url або quote.

Наприклад:

[b]текст[/b]
[url]http://google.com[/url]

ВВ-теги можуть бути вкладеними. Але тег не може бути вкладеним сам у себе, наприклад:

Може бути:
[url] [b]http://google.com[/b] [/url]
[quote] [b]текст[/b] [/quote]

Не може бути:
[b][b]text[/b][/b]

Теги можуть мати розрив рядків, це допустимо:

[quote]
  [b]текст[/b]
[/quote]

Створіть регулярний вираз для пошуку всіх BB-тегів та їх вмісту.

Наприклад:

let regexp = /ваш регулярний вираз/прапорці;

let str = "..[url]http://google.com[/url]..";
alert( str.match(regexp) ); // [url]http://google.com[/url]

Якщо теги вкладені, тоді необхідно шукати зовнішній тег (за бажанням, можна продовжити пошук всередині його вмісту):

let regexp = /ваш регулярний вираз/прапорці;

let str = "..[url][b]http://google.com[/b][/url]..";
alert( str.match(regexp) ); // [url][b]http://google.com[/b][/url]

Відкриваючий тег – це \[(b|url|quote)].

Потім, щоб знайти все, до закриваючого тегу – використаємо вираз .*? з прапорцем s щоб знайти будь-які символи, включно з новим рядком, а потім додати зворотне посилання до закриваючого тегу.

Цілий вираз: \[(b|url|quote)\].*?\[/\1].

У дії:

let regexp = /\[(b|url|quote)].*?\[\/\1]/gs;

let str = `
  [b]hello![/b]
  [quote]
    [url]http://google.com[/url]
  [/quote]
`;

alert( str.match(regexp) ); // [b]hello![/b],[quote][url]http://google.com[/url][/quote]

Зверніть увагу, що крім екранування [, нам необхідно екранувати слеш у закриваючому тегу [\/\1], бо зазвичай слеш завершує вираз.

Створіть регулярний вираз для пошуку рядків у подвійних лапках "...".

Рядки повинні підтримувати екранування за допомогою зворотнього слеша, аналогічно з рядками в JavaScript. Наприклад, лапки можуть бути вставлені як \", новий рядок як \n, та сам зворотній слеш як \\.

let str = "Як ось \"тут\".";

Зокрема, зверніть увагу, що екрановані лапки \" не завершують рядок.

Тому нам необхідно шукати від одних лапок до інших, ігноруючи екрановані лапки на нашому шляху.

У цьому і полягає основна складність завдання, адже без цієї умови – рішення було б елементарним.

Приклади відповідних рядків:

.. "test me" ..
.. "Скажи \"Привіт\"!" ... (екрановані рядки всередині)
.. "\\" ..  (подвійний слеш всередині)
.. "\\ \"" ..  (подвійний слеш та екрановані лапки всередині)

У JavaScript нам потрібно подвоювати слеші, щоб передати їх в рядок, як тут:

let str = ' .. "протестуй мене" .. "Скажи \\"Привіт\\"!" .. "\\\\ \\"" .. ';

// Рядок у пам’яті
alert(str); //  .. "протестуй мене" .. "Скажи \"Привіт\"!" .. "\\ \"" ..

Рішення: /"(\\.|[^"\\])*"/g.

Крок за кроком:

  • Спочатку шукаємо відкриваючі лапки "
  • Потім, якщо є зворотній слеш \\ (ми повинні подвоїти його у виразі, тому що це спеціальний символ), то після нього також підійде будь-який символ (крапка).
  • В іншому випадку, беремо будь-який символ, крім лапок (це означало б кінець рядка) та зворотнього слешу (щоб запобігти поодиноким зворотнім слешам, бо вони використовуються тільки з іншими символами після них): [^"\\]
  • …І так далі, до закриваючих лапок.

У дії:

let regexp = /"(\\.|[^"\\])*"/g;
let str = ' .. "протестуй мене" .. "Скажи \\"Привіт\\"!" .. "\\\\ \\"" .. ';

alert( str.match(regexp) ); // "протестуй мене","Скажи \"Привіт\"!","\\ \""

Напишіть регулярний вираз, який шукає тег <style...>. Шаблон має шукати цілий тег: він може як і не мати атрибутів <style>, так і мати їх декілька <style type="..." id="...">.

…Проте регулярний вираз не повинен знаходити <styler>!

Наприклад:

let regexp = /ваш регулярний вираз/g;

alert( '<style> <styler> <style test="...">'.match(regexp) ); // <style>, <style test="...">

Початок шаблону є очевидним: <style.

…Однак далі, ми не можемо просто прописати <style.*?>, тому що <styler> відповідає цьому виразу.

Потім, після <style має бути або пробіл, за яким може бути ще щось, або закриття тегу >.

Мовою регулярних виразів: <style(>|\s.*?>).

У дії:

let regexp = /<style(>|\s.*?>)/g;

alert( '<style> <styler> <style test="...">'.match(regexp) ); // <style>, <style test="...">
Навчальна карта