Почему порядок операций важен
Очистка адреса — это последовательность из нескольких шагов, и применение их в неверном порядке даёт неожиданный результат. Общая последовательность разобрана на примере функции транслита в статье про PHP, JavaScript и Python: сначала замена букв по таблице, затем удаление лишних символов, затем схлопывание разделителей, и только в конце — обрезка краёв.
Удаление знаков препинания без замены на разделитель
Некоторые символы — кавычки, апострофы, точки в сокращениях — должны исчезать полностью, а не превращаться в дефис. Если их не убрать заранее, они попадут под общее правило замены посторонних символов и создадут лишний разделитель там, где его быть не должно.
Паттерн: /['"«»`’]+/g
Пример: ООО «Садовый мир» → OOO Sadovyj mir (кавычки исчезли, не оставив следа)
Замена всего постороннего на разделитель
После того как «тихие» символы удалены, всё остальное, что не входит в латиницу и цифры, превращается в разделитель — этот паттерн и делает основную работу очистки.
Паттерн: /[^a-z0-9]+/g
Замена: на выбранный разделитель ('-' или '_')
Именно этот шаг превращает пробелы, эмодзи и оставшиеся спецсимволы, разобранные в статье про спецсимволы и эмодзи в заголовках, в единообразные дефисы.
Схлопывание повторяющихся разделителей
После предыдущего шага в строке могут оказаться несколько разделителей подряд — например, если рядом стояли два разных символа, каждый из которых заменился на дефис.
Паттерн: /-{2,}/g
Замена: на один дефис
Для более общего варианта, работающего с любым выбранным разделителем, а не только дефисом, паттерн строится динамически с учётом текущего символа-разделителя — важно экранировать его в регулярном выражении, если это символ вроде точки, у которого есть особый смысл в синтаксисе регулярных выражений.
Частая ошибка в этом шаге. Паттерн вида /--/g заменяет только пары из ровно двух дефисов и не сработает на строке с тремя дефисами подряд. Квантификатор {2,} означает «два или больше» и покрывает любое количество повторов правильно.
Обрезка разделителей по краям строки
Последний шаг — убрать разделитель, если он оказался в самом начале или в самом конце адреса, что часто случается после удаления первого или последнего слова фразы.
Паттерн: /^-+|-+$/g
Замена: на пустую строку
Вертикальная черта в паттерне означает «или»: выражение находит разделители либо в начале, либо в конце строки и убирает оба случая одним проходом.
Полный пример последовательности на JavaScript
function cleanUrl(text, sep = '-') {
const escaped = sep === '-' ? '\\-' : sep;
return text
.replace(/['"«»`’]+/g, '') // тихое удаление
.replace(/[^a-z0-9]+/g, sep) // замена на разделитель
.replace(new RegExp(`[${escaped}]{2,}`, 'g'), sep) // схлопывание
.replace(new RegExp(`^[${escaped}]+|[${escaped}]+$`, 'g'), ''); // обрезка краёв
}
Эта последовательность — ровно то, что происходит внутри пакетного режима генератора на этом сайте, только записанное явно шаг за шагом.
Тестовые случаи для проверки паттернов
| Вход | Ожидаемый результат |
|---|---|
| «Тест» — пример! | test-primer |
| —Пример— | primer |
| Слово с пробелами | slovo-s-probelami |
| А---Б | a-b |
Чек-лист
- Сначала «тихое» удаление кавычек и апострофов, затем замена остального на разделитель.
- Схлопывание использует квантификатор «два и более», а не фиксированное число повторов.
- Обрезка краёв выполняется последним шагом, после всех замен.
- Специальные символы разделителя экранируются, если для него выбран знак вроде точки.
- Набор паттернов проверен на тестовых случаях с разными комбинациями символов.
Производительность регулярных выражений на больших объёмах
При обработке каталога в десятки тысяч строк регулярное выражение стоит компилировать один раз вне цикла, а не создавать заново для каждой строки — в языках вроде JavaScript и Python повторная компиляция одного и того же паттерна заметно замедляет обработку на больших объёмах, хотя для одиночного адреса разница незаметна.
Пограничные случаи, которые стоит протестировать отдельно
- Строка, состоящая только из посторонних символов, — результат должен быть пустой строкой, а не набором дефисов.
- Одно единственное слово без пробелов — обрезка краёв не должна его повредить.
- Разделитель в самом начале и в самом конце одновременно.
- Пустая строка на входе — функция не должна выбрасывать ошибку.
Частые вопросы
В каком порядке применять регулярные выражения при очистке адреса?
Сначала тихое удаление кавычек и апострофов без замены на разделитель, затем замена всех остальных посторонних символов на разделитель, затем схлопывание повторов и в конце — обрезка краёв строки.
Почему после замены символов в адресе остаются двойные дефисы?
Потому что шаг схлопывания повторов не выполнен или использует неверный квантификатор. Паттерн должен ловить любое количество повторов подряд, а не только пары.
Нужно ли экранировать символ разделителя в регулярном выражении?
Да, если выбранный разделитель — специальный символ регулярных выражений, например точка. Для обычного дефиса и подчёркивания экранирование чаще всего не критично, но для надёжности лучше делать его всегда.
Готовый адрес можно собрать прямо сейчас: откройте генератор ЧПУ — он транслитерирует фразу по таблице Яндекса, уберёт служебные слова и покажет длину адреса.