Вложенные регулярные выражения смотрят вперед и смотрят назад - PullRequest
9 голосов
/ 23 октября 2011

У меня проблемы с вложенным символом '+' / '-' в регулярном выражении.

Допустим, я хочу изменить '*' в строке на '%', и скажем, что '\' экранирует следующий символ. (Превращение регулярного выражения в sql, как команда ^^).

Итак, строка

  • '*test*' следует изменить на '%test%',
  • '\\*test\\*' -> '\\%test\\%', но
  • '\*test\*' и '\\\*test\\\*' должны остаться прежними.

Я пытался:

(?<!\\)(?=\\\\)*\*      but this doesn't work
(?<!\\)((?=\\\\)*\*)    ...
(?<!\\(?=\\\\)*)\*      ...
(?=(?<!\\)(?=\\\\)*)\*  ...

Какое правильное регулярное выражение будет соответствовать '*' в приведенных выше примерах?

В чем разница между (?<!\\(?=\\\\)*)\* и (?=(?<!\\)(?=\\\\)*)\* или, если они по сути неверны, разница между регулярными выражениями, имеющими такую ​​визуальную конструкцию?

Ответы [ 5 ]

11 голосов
/ 23 октября 2011

Чтобы найти неэкранированный символ, вам нужно найти символ, которому предшествует четное число (или ноль) escape-символов.Это относительно просто.

(?<=(?<!\\)(?:\\\\)*)\*        # this is explained in Tim Pietzcker' answer

К сожалению, многие движки регулярных выражений не поддерживают просмотр с переменной длиной, поэтому мы должны заменить его на просмотр:

(?=(?<!\\)(?:\\\\)*\*)(\\*)\*  # also look at ridgerunner's improved version

Замените это содержимым группы 1 и знаком %.

Пояснение

(?=           # start look-ahead
  (?<!\\)     #   a position not preceded by a backslash (via look-behind)
  (?:\\\\)*   #   an even number of backslashes (don't capture them)
  \*          #   a star
)             # end look-ahead. If found,
(             # start group 1
  \\*         #   match any number of backslashes in front of the star
)             # end group 1
\*            # match the star itself

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

9 голосов
/ 23 октября 2011

Хорошо, так как Тим решил не обновлять свое регулярное выражение с моими предлагаемыми модами (и ответ Томалака не такой обтекаемый), вот мое рекомендуемое решение:

Заменить: ((?<!\\)(?:\\\\)*)\* на $1%

Здесь он представлен в виде закомментированного фрагмента PHP:

// Replace all non-escaped asterisks with "%".
$re = '%             # Match non-escaped asterisks.
    (                # $1: Any/all preceding escaped backslashes.
      (?<!\\\\)      # At a position not preceded by a backslash,
      (?:\\\\\\\\)*  # Match zero or more escaped backslashes.
    )                # End $1: Any preceding escaped backslashes.
    \*               # Unescaped literal asterisk.
    %x';
$text = preg_replace($re, '$1%', $text);

Приложение: Необязательное решение JavaScript

Приведенное выше решение требует просмотра назад, поэтому оно не будет работатьв JavaScript.Следующее решение JavaScript не использует lookbehind:

text = text.replace(/(\\[\S\s])|\*/g,
    function(m0, m1) {
        return m1 ? m1 : '%';
    });

Это решение заменяет каждый экземпляр backslash-everything на себя, а каждый экземпляр * звездочкус % знаком процента.

Редактировать 2011-10-24: Исправлена ​​версия Javascript для правильной обработки таких случаев, как: **text**.(Спасибо Алану Муру за указание на ошибку в предыдущей версии.)

5 голосов
/ 24 октября 2011

Другие показали, как это можно сделать с помощью lookhehind, но я бы хотел привести аргумент в пользу того, что вообще не использовал lookarounds.Рассмотрим это решение ( демо здесь ):

s/\G([^*\\]*(?:\\.[^*\\]*)*)\*/$1%/g;

Основная часть регулярного выражения, [^*\\]*(?:\\.[^*\\]*)*, является примером идиомы "развернутого цикла" Фридла.Он потребляет как можно больше отдельных символов, кроме звездочки или обратной косой черты, или пар символов, состоящих из обратной косой черты, за которой следует что угодно.Это позволяет избежать использования звездочек без экранирования, независимо от того, сколько им предшествует обратной косой черты (или других символов).

\G привязывает каждое совпадение к позиции, где закончилось предыдущее совпадение, или к началуввод, если это первая попытка совпадения.Это препятствует тому, чтобы механизм регулярных выражений просто пропускал экранированные обратные слэши и в любом случае сопоставлял неэкранированные звездочки.Таким образом, каждая итерация контролируемого совпадения /g потребляет все до следующей неэкранированной звездочки, захватывая все, кроме звездочки в группе # 1.Затем он снова подключается, и * заменяется на %.

Я думаю, что это, по крайней мере, так же легко читаемо, как приближающийся взгляд, и легче для понимания.Для этого требуется поддержка \G, поэтому он не будет работать в JavaScript или Python, но он прекрасно работает в Perl.

3 голосов
/ 23 октября 2011

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

Поиск

(?<=(?<!\\)(?:\\\\)*)\*

и заменить на %.

Пояснение:

(?<=       # Assert that it's possible to match before the current position...
 (?<!\\)   # (unless there are more backslashes before that)
 (?:\\\\)* # an even number of backslashes
)          # End of lookbehind
\*         # Then match an asterisk
0 голосов
/ 17 октября 2012

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

  • Обратные слэши экранируют любой символ после них, а не только другие обратные слэши.Таким образом, (\\.)* съест целую цепочку сбежавших персонажей, независимо от того, являются они обратной косой чертой или нет.Вам не нужно беспокоиться о четных или нечетных слешах;просто проверьте одиночную \ в начале или конце цепочки ( JavaScript-решение Ridgerunner действительно использует это преимущество).

  • Опасения не являютсяЕдинственный способ убедиться, что вы начинаете с первого обратного слеша в цепочке.Вы можете просто найти символ без обратной косой черты (или начало строки).

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

/(?!<\\)(\\.)*\*/g

И строка замены:

"$1%"

Это работает в .NET , что позволяет смотреть за спиной, и должно работать для вас в Perl.Это можно сделать в JavaScript, но без lookbehinds или якоря \G я не вижу способа сделать это в одну строку.Обратный вызов Риджеруннера должен работать, как и цикл:

var regx = /(^|[^\\])(\\.)*\*/g;
while (input.match(regx)) {
    input = input.replace(regx, '$1$2%');
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...