Регулярное выражение PHP и смежные группы захвата

130
10

Я использую группы захвата в регулярных выражениях в первый раз, и мне интересно, что моя проблема, поскольку я предполагаю, что механизм регулярных выражений просматривает строку слева направо.


Я пытаюсь преобразовать строку UpperCamelCase в строку с дефисами в нижнем регистре, например:


HelloWorldThisIsATest => hello-world-this-is-a-test

Мое предварительное условие - это алфавитная строка, поэтому мне не нужно беспокоиться о числах или других персонажах. Вот что я пробовал:


mb_strtolower(preg_replace('/([A-Za-z])([A-Z])/', '$1-$2', "HelloWorldThisIsATest"));

Результат:


hello-world-this-is-atest

Это почти то, что я хочу, за исключением того, что между a и test должен быть дефис. Я уже включил A-Z в свою первую группу захвата, поэтому я бы предположил, что движок видит AT и переносит это.


Что я делаю неправильно?

спросил(а) 2021-01-28T00:39:03+03:00 3 месяца, 1 неделя назад
1
Решение
115

Причина вашего регулярного выражения не будет работать: совпадение совпадений


    Ваше регулярное выражение соответствует sA в IsATest, что позволяет вам вставить - между s и A
    Чтобы вставить - между A и T, регулярное выражение должно соответствовать AT.
    Это невозможно, потому что A уже согласован как часть sA. Вы не можете иметь совпадающие совпадения в прямом регулярном выражении.
    Неужели все надежды потеряны? Нет! Это идеальная ситуация для поиска.

Сделайте это в двух простых линиях


Вот простой способ сделать это с помощью регулярного выражения:


$regex = '~(?<=[a-zA-Z])(?=[A-Z])~';
echo strtolower(preg_replace($regex,"-","HelloWorldThisIsATest"));

Смотрите вывод в нижней части php demo:

Выход: hello-world-this-is-a-test



Снова добавит объяснение.:)


    Регулярное выражение не соответствует никаким символам. Скорее, он нацеливает позиции в строке: позиции между изменением в случае буквы. Для этого он использует lookbehind и lookahead.
    (?<=[a-zA-Z]) lookbehind утверждает, что предшествующая текущая позиция - это буква
    (?=[A-Z]) lookahead утверждает, что то, что следует за текущей позицией, является строчной буквой.
    Мы просто заменяем эти позиции на - и преобразуем лот в нижний регистр.

Если вы внимательно посмотрите на regex101 screen, вы увидите строки между словами, в которых соответствует регулярное выражение.


Ссылка


ответил(а) 2021-01-28T00:39:03+03:00 3 месяца, 1 неделя назад
107

Я разделил два регулярных выражения для простоты:


preg_replace(array('/([a-z])([A-Z])/', '/([A-Z]+)([A-Z])/'), '$1-$2', $string);

Он обрабатывает строку дважды, чтобы найти:


    строчные буквы → верхние регистры
    несколько заглавных букв, за которыми следует другая прописная буква

Это будет иметь следующее поведение:


ThisIsHTMLTest -> This-Is-HTML-Test
ThisIsATest -> This-Is-A-Test

В качестве альтернативы используйте прогнозное утверждение (это повлияет на повторное использование последней заглавной буквы, которая использовалась в предыдущем матче):


preg_replace('/([A-Z]+|[a-z]+)(?=[A-Z])/', '$1-', $string);

ответил(а) 2021-01-28T00:39:03+03:00 3 месяца, 1 неделя назад
97

Чтобы исправить интересный случай использования Джек, упомянутый в ваших комментариях (избегайте расщепления сокращений), я пошел с маршрутом zx81 с использованием lookahead и lookbehinds.


(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])

Вы можете разбить его на две части для объяснения:


Первая часть


(?<=                     look behind to see if there is:
[a-z] any character of: 'a' to 'z'
) end of look-behind
(?= look ahead to see if there is:
[A-Z] any character of: 'A' to 'Z'
) end of look-ahead

(TL; DR: соответствие между строками шаблона CamelCase.)


Вторая часть


(?<=                     look behind to see if there is:
[A-Z] any character of: 'A' to 'Z'
) end of look-behind
(?= look ahead to see if there is:
[A-Z] any character of: 'A' to 'Z'
[a-z] any character of: 'a' to 'z'
) end of look-ahead

(TL; DR: специальный случай, совпадение между аббревиатурой и шаблоном CamelCase)


Итак, ваш код будет выглядеть следующим образом:


mb_strtolower(preg_replace('/(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/', '-', "HelloWorldThisIsATest"));

Демонстрация матчей
Демо-код

ответил(а) 2021-01-28T00:39:03+03:00 3 месяца, 1 неделя назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема