Към съдържанието
wannacode

Търсене и замяна

Проблем

Извлечението от банката

Банката ти праща текст с плащанията за месеца. Искаш всяка сума поотделно, за да ги събереш:

const statement = "Наем 450,00 € на 01.10, ток 87,35 € на 05.10, вода 23,10 € на 12.10";
console.log(/\d+,\d{2} €/.test(statement));
Изход
true
test казва само „да, има сума някъде“. 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 до 5
ERROR от 24 до 29
Общо: 2
m.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);
Отговор

Идеята

Групи: части от съвпадението

Скобите в шаблона правят група. Съвпадението помни какво е съвпаднало с всяка група поотделно:

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.2026
15 / 11 / 2026
2026-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]);
}
Стъпка по стъпка, с думи
  1. Шаблонът за дата има три двойки скоби: три групи.
  2. match без флаг g търси първото съвпадение. m е масив с него и групите, или null.
  3. match намира датата: от индекс 7 до 17.
  4. Всяка група помни своята част: група 1 е 15, група 2 е 11, група 3 е 2026.
  5. Частите стоят в масива m, всяка под своя индекс. m[0] е цялото съвпадение.
  6. m[0] е цялото съвпадение: 15.11.2026.
  7. Трите групи поотделно.
  8. Наредени в обратен ред, частите дават 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]);
Изход
2
5 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/2026
Тук не ти трябва matchAll: 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. На кой ред е грешката?

Избери реда с грешката.

Идеята

Флагове и редове

Флаговете са букви след шаблона. От лог файл с няколко реда вземаш съобщенията за грешки, написани с главни или малки букви:

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. Намери и замени. Замени думата „старо“ с „ново“ навсякъде, където е цяла дума, и кажи колко замени си направил. Части от други думи („старомодно“) не се пипат.

Вход
Това е старо съдържание. Още старо съдържание, не старомодно.
Изход
Това е ново съдържание. Още ново съдържание, не старомодно.
Замени: 2

2. Думи с префикс. На първия ред е префиксът, на втория текстът. Отпечатай всяка дума, която започва с префикса. Шаблонът е сглобен от текст, затова е new RegExp(...).

Вход
app
apple application banana appreciate snapple
Изход
apple
application
appreciate

3. Адреси в текст. Извади всички уеб адреси, които започват с http:// или https://. Точката в края на изречението не е част от адреса.

Вход
Виж сайта https://www.example.com и този: http://www.test.com. Документите са на https://docs.example.com/guide/start.
Изход
https://www.example.com
http://www.test.com
https://docs.example.com/guide/start

4. Дати към 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.

Тест

Провери се

1. Какво дава текст.matchAll(/…/g)?
2. Шаблонът е /(\d{2})\.(\d{2})\.(\d{4})/, а текстът е 15.11.2026. Какво е m[2]?
3. Какво дава "05.03.2026".replace(/(\d+)\.(\d+)\.(\d+)/, "$2/$1")?
4. Защо "<.+>" хваща целия текст "<b>Цена</b>", а не само "<b>"?
5. Какво прави флагът m?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·