Вложенное регулярное выражение lookahead и lookbehind

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

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

в строке

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

пробовал:

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

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

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

5 ответов


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

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

к сожалению, многие движки regex не поддерживают look-behind переменной длины, поэтому мы должны заменить look-ahead:

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

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

объяснение

(?=           # 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

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


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

заменить: ((?<!\)(?:\\)*)\* С %

вот это в форме комментируются, PHP snippett:

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

добавление: не-lookaround JavaScript решение

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

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

это решение заменяет каждый экземпляр обратный слеш-ничего С собой, и каждый экземпляр * Asterisk с % знак процента.

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


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

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

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

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

Я думаю, что это, по крайней мере, так же читаемо, как lookaround подходы, и легче понять. Это требует поддержки \G, поэтому он не будет работать в JavaScript или Python, но он отлично работает в Perl.


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

искать

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

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

объяснение:

(?<=       # 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

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

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

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

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

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

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

"%"

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

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

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