Търсене и замяна
Проблем
Извлечението от банката
Банката ти праща текст с плащанията за месеца. Искаш всяка сума поотделно, за да ги събереш:
const statement = "Наем 450,00 € на 01.10, ток 87,35 € на 05.10, вода 23,10 € на 12.10";console.log(/\d+,\d{2} €/.test(statement));truetest казва само „да, има сума някъде“. replace би сменил сумите, но не ти ги дава. Трябва ти начин да намериш всяко съвпадение и да го вземеш.Идеята
matchAll
За търсене текстът има метода matchAll:
const statement = "Наем 450,00 € на 01.10, ток 87,35 € на 05.10, вода 23,10 € на 12.10";const amount = /\d+,\d{2} €/g;for (const m of statement.matchAll(amount)) { console.log(m[0]);}450,00 €87,35 €23,10 €- Шаблонът е с флаг
g: „всички съвпадения“. Без негоmatchAllспира сTypeError. statement.matchAll(amount)дава съвпаденията едно по едно, отляво надясно. Затова е въвfor…of: всяко завъртане получава следващото, докато свършат.mе едно намерено съвпадение: масив, в койтоm[0]е текстът му.
По-точно, за любопитните
matchс флагgдава направо масив с текстовете на съвпаденията. Безgдава само първото съвпадение, с групите му (null, ако няма).
const statement = "Наем 450,00 € на 01.10, ток 87,35 € на 05.10, вода 23,10 € на 12.10";console.log(statement.match(/\d+,\d{2} €/g));console.log(statement.match(/\d+,\d{2} €/)[0]);[ '450,00 €', '87,35 €', '23,10 €' ]450,00 €Виж го
Къде е съвпадението
Освен текста, съвпадението знае и къде е намерено:
const log = "ERROR диск; INFO старт; ERROR мрежа";let count = 0;for (const m of log.matchAll(/ERROR/g)) { count++; console.log(`${m[0]} от ${m.index} до ${m.index + m[0].length}`);}console.log(`Общо: ${count}`);ERROR от 0 до 5ERROR от 24 до 29Общо: 2m.index е индексът на първия символ на съвпадението. Краят е m.index + m[0].length: индексът след последния, както при slice. Затова log.slice(m.index, m.index + m[0].length) е същото като m[0].Пробвай
Цифра или число?
Какво ще отпечата програмата?
let result = "";for (const m of "Маса 12, 4 души, 2 бутилки".matchAll(/\d+/g)) { result += `[${m[0]}]`;}console.log(result);\d+ е „една или повече цифри“, затова „12“ е едно съвпадение. Със \d без + всяка цифра би била отделно: [1][2][4][2]. Интервалът и запетаята прекъсват поредицата, затова 12 и 4 не се сливат.
Идеята
Групи: части от съвпадението
Скобите в шаблона правят група. Съвпадението помни какво е съвпаднало с всяка група поотделно:
const date = /(\d{2})\.(\d{2})\.(\d{4})/;const m = "Падеж: 15.11.2026".match(date);if (m) { console.log(m[0]); console.log(m[1] + " / " + m[2] + " / " + m[3]); console.log(m[3] + "-" + m[2] + "-" + m[1]);}15.11.202615 / 11 / 20262026-11-15- Групите се номерират по отварящите скоби, отляво надясно, от 1.
m[1]е група 1.m[0]е цялото съвпадение.matchбез флагgе за случаите, когато ти трябва само първото съвпадение. Ако няма такова,mеnullиif (m)го прескача.
Така от една дата вземаш поотделно деня, месеца и годината и ги нареждаш както ти трябва.
const date = /(\d{2})\.(\d{2})\.(\d{4})/;const m = "Падеж: 15.11.2026".match(date);if (m) { console.log(m[0]); console.log(m[1] + " / " + m[2] + " / " + m[3]); console.log(m[3] + "-" + m[2] + "-" + m[1]);}Стъпка по стъпка, с думи
- Шаблонът за дата има три двойки скоби: три групи.
- match без флаг g търси първото съвпадение. m е масив с него и групите, или null.
- match намира датата: от индекс 7 до 17.
- Всяка група помни своята част: група 1 е 15, група 2 е 11, група 3 е 2026.
- Частите стоят в масива m, всяка под своя индекс. m[0] е цялото съвпадение.
- m[0] е цялото съвпадение: 15.11.2026.
- Трите групи поотделно.
- Наредени в обратен ред, частите дават 2026-11-15.
По-точно, за любопитните
(?:...)е група, която само държи частите заедно, без да ги помни. Например(?:\+359|0)8[789]\d{7}не прави група 1.
m.length - 1е броят на групите: масивът има цялото съвпадение и по едно място за всяка група.- Ако група не е участвала в съвпадението (например
(,\d{2})?я е нямало), мястото ѝ еundefined.
const m = "Бакшиш 5 €".match(/(\d+)(,\d{2})? €/);console.log(m.length - 1);console.log(m[1], m[2]);25 undefinedИдеята
Групи с имена и замяна
(?<име>...), а после m.groups.име. Ред от сметка в кафене:const item = /(?<qty>\d+) x (?<name>[А-Яа-я ]+) по (?<price>\d+\.\d{2})/g;for (const m of "2 x Кафе с мляко по 3.20; 1 x Кроасан по 2.90".matchAll(item)) { const qty = Number(m.groups.qty); const price = Number(m.groups.price); console.log(`${m.groups.name}: ${qty * price} €`);}Кафе с мляко: 6.4 €Кроасан: 2.9 €replace: в текста за замяна $1 е група 1, а $<име> е групата с това име. Датите от български към ISO вид и към американски:const text = "Срок: 15.11.2026, напомняне: 08.11.2026";console.log(text.replace(/(\d{2})\.(\d{2})\.(\d{4})/g, "$3-$2-$1"));console.log(text.replace(/(?<d>\d{2})\.(?<m>\d{2})\.(?<y>\d{4})/g, "$<m>/$<d>/$<y>"));Срок: 2026-11-15, напомняне: 2026-11-08Срок: 11/15/2026, напомняне: 11/08/2026matchAll: replace разбира групите сам.По-точно, за любопитните
$в текста за замяна е специален. Истински знак за долар е$$:
console.log("Цена: 5".replace(/(\d+)/, "$$$1"));Цена: $5- Групата може да се използва и в самия шаблон:
\1значи „същото като група 1“. Така се хващат повторени думи. Виж картата за флаговете.
Пробвай
Колко взима +?
Искаш всички HTML етикети от реда:
Какво ще отпечата програмата?
const html = "<b>Цена:</b> <i>12 €</i>";for (const m of html.matchAll(/<.+>/g)) { console.log(m[0]);}Едно съвпадение, целият ред. .+ взима колкото може повече: стига до края на текста, после връща назад символ по символ, докато намери >. Първото > отзад е последното в реда. Такива квантификатори се казват жадни.
Идеята
Жадно и нежадно
? след квантификатор го прави нежаден: той взима колкото може по-малко и спира при първата възможност.
const html = "<b>Цена:</b> <i>12 €</i>";for (const m of html.matchAll(/<.+?>/g)) { console.log(m[0]);}console.log(html.replace(/<[^>]+>/g, ""));<b></b><i></i>Цена: 12 €<.+?>е „<, най-малко един символ и първото>след него“.<[^>]+>прави същото по друг начин: „<, символи, които не са>, и>“. Той не може да прескочи>, затова е и по-бърз. С него махаш етикетите и остава само текстът.
| Жаден | Нежаден |
|---|---|
* |
*? |
+ |
+? |
? |
?? |
{2,5} |
{2,5}? |
Пробвай
Намери грешката
Програмата трябва да отпечата трите номера на поръчки, а отпечатва само #1042. На кой ред е грешката?
Избери реда с грешката.
g, затова match дава само първото съвпадение. С /#\d+/g match дава масив с текстовете на всички.Идеята
Флагове и редове
Флаговете са букви след шаблона. От лог файл с няколко реда вземаш съобщенията за грешки, написани с главни или малки букви:
const log = "INFO: Старт\nERROR: Няма връзка с базата\nINFO: Повторен опит\nerror: Диск 95%";const p = /^error: (.+)$/gim;for (const m of log.matchAll(p)) { console.log(m[1]);}Няма връзка с базатаДиск 95%i: главните и малките букви са еднакви.m(multiline) прави^и$начало и край на всеки ред. Без него те са само началото и краят на целия текст и шаблонът няма да намери нищо.sпозволява на.да хваща и нов ред.gвече познаваш: без негоmatchAllне работи.
По-точно, за любопитните
Две неща, които ще срещнеш в чужди шаблони:
- Обратна препратка (backreference)
\1: „същият текст като група 1“. Така се хващат повторени думи. - Поглед напред (lookahead)
(?=...): „оттук нататък някъде има ...“, без да взима символи. Няколко такива един след друг са удобни за пароли: „има цифра“ и „има главна буква“, и поне 8 символа.
const twice = /(?<!\p{L})(\p{L}+) \1(?!\p{L})/gu;for (const m of "Плащането е е прието, моля моля проверете.".matchAll(twice)) { console.log("Повторено: " + m[1]);}const strong = /^(?=.*\d)(?=.*[A-Z]).{8,}$/;console.log(strong.test("parola12"), strong.test("Parola12"));Повторено: еПовторено: моляfalse true(?!...) е обратното: „оттук нататък няма ...“. Има и поглед назад: (?<=...) и (?<!...). С тях е и границата на думата на кирилица горе: \b в JavaScript познава само латиница.
Упражнение
Намери, извлечи, замени
Реши задачите в редактора си. Текстът е на един ред от клавиатурата. В урока „Файлове“ ще ги направиш и с файл.
1. Намери и замени. Замени думата „старо“ с „ново“ навсякъде, където е цяла дума, и кажи колко замени си направил. Части от други думи („старомодно“) не се пипат.
Това е старо съдържание. Още старо съдържание, не старомодно.Това е ново съдържание. Още ново съдържание, не старомодно.Замени: 22. Думи с префикс. На първия ред е префиксът, на втория текстът. Отпечатай всяка дума, която започва с префикса. Шаблонът е сглобен от текст, затова е new RegExp(...).
appapple application banana appreciate snappleappleapplicationappreciate3. Адреси в текст. Извади всички уеб адреси, които започват с http:// или https://. Точката в края на изречението не е част от адреса.
Виж сайта https://www.example.com и този: http://www.test.com. Документите са на https://docs.example.com/guide/start.https://www.example.comhttp://www.test.comhttps://docs.example.com/guide/start4. Дати към ISO. Превърни всяка дата дд.мм.гггг в текста във вида гггг-мм-дд с един replace.
Договор от 03.10.2026, плащане до 15.10.2026.Договор от 2026-10-03, плащане до 2026-10-15.Още задачи
- Сбор от извлечението. Вземи сумите от първата карта с група за еврото и група за центовете, събери ги и отпечатай „Общо: 560,45 €“.
- Хаштагове. Извади всички думи, които започват с
#, от публикация на кирилица и латиница. Помисли защо ти трябват\p{L}и флагътu. - Без етикети. Махни HTML етикетите от
<p>Срокът е <b>15.11</b>.</p>и отпечатай чистия текст. - Телефони в текст. Намери всички мобилни номера в текст и ги отпечатай във вида
+359 88 812 3456с групи иreplace.
Тест