Regex для управления экранированными символами для таких элементов, как строковые литералы - PullRequest
9 голосов
/ 10 января 2009

Я хотел бы иметь возможность сопоставлять строковый литерал с опцией экранированных кавычек. Например, я хотел бы иметь возможность искать «это« тест с экранированными »значениями« ок »и правильно распознавать обратную косую черту как escape-символ. Я пробовал решения, подобные следующим:

import re
regexc = re.compile(r"\'(.*?)(?<!\\)\'")
match = regexc.search(r""" Example: 'Foo \' Bar'  End. """)
print match.groups() 
# I want ("Foo \' Bar") to be printed above

Посмотрев на эту проблему, вы обнаружите простую проблему, заключающуюся в том, что используемый escape-символ "\" не может быть экранирован сам по себе. Я не могу понять, как это сделать. Я хотел решение, подобное следующему, но отрицательные утверждения за фиксированной длиной должны быть фиксированной длины:

# ...
re.compile(r"\'(.*?)(?<!\\(\\\\)*)\'")
# ...

Кто-нибудь из гуру регулярных выражений способен решить эту проблему? Спасибо.

Ответы [ 6 ]

16 голосов
/ 28 марта 2011

re_single_quote = r "'[^'\\]*(?:\\.[^'\\]*)*'"

Во-первых, обратите внимание, что ответ MizardX точен на 100%. Я просто хотел бы добавить некоторые дополнительные рекомендации относительно эффективности. Во-вторых, я хотел бы отметить, что эта проблема была решена и оптимизирована давно - см .: Освоение регулярных выражений (3-е издание) (которая подробно описывает эту конкретную проблему - очень рекомендуется).

Сначала давайте посмотрим на подвыражение, чтобы соответствовать одной строке в кавычках, которая может содержать экранированные одинарные кавычки. Если вы собираетесь разрешить экранированные одинарные кавычки, лучше хотя бы разрешить экранированные экранирования (что и делает ответ Дугласа Лидера). Но до тех пор, пока вы это делаете, его так же легко позволить избежать чего-либо еще. С этими требованиями. MizardX - единственный, кто получил правильное выражение. Здесь он представлен как в коротком, так и в длинном формате (и я позволил себе написать это в режиме VERBOSE, с большим количеством описательных комментариев - что вы должны всегда делать для нетривиальных регулярных выражений):

# MizardX's correct regex to match single quoted string:
re_sq_short = r"'((?:\\.|[^\\'])*)'"
re_sq_long = r"""
    '           # Literal opening quote
    (           # Capture group $1: Contents.
      (?:       # Group for contents alternatives
        \\.     # Either escaped anything
      | [^\\']  # or one non-quote, non-escape.
      )*        # Zero or more contents alternatives.
    )           # End $1: Contents.
    '
    """

Это работает и правильно сопоставляет все следующие строковые тесты:

text01 = r"out1 'escaped-escape:        \\ ' out2"
test02 = r"out1 'escaped-quote:         \' ' out2"
test03 = r"out1 'escaped-anything:      \X ' out2"
test04 = r"out1 'two escaped escapes: \\\\ ' out2"
test05 = r"out1 'escaped-quote at end:   \'' out2"
test06 = r"out1 'escaped-escape at end:  \\' out2"

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

# Better regex to match single quoted string:
re_sq_short = r"'((?:[^\\']|\\.)*)'"
re_sq_long = r"""
    '           # Literal opening quote
    (           # $1: Contents.
      (?:       # Group for contents alternatives
        [^\\']  # Either a non-quote, non-escape,
      | \\.     # or an escaped anything.
      )*        # Zero or more contents alternatives.
    )           # End $1: Contents.
    '
    """

"разворачивая-The-Loop":

Это немного лучше, но может быть еще более улучшено (значительно), применяя эффективность разматывания Джеффри Фридла * (от MRE3 ). Вышеупомянутое регулярное выражение не является оптимальным, потому что оно должно тщательно применять звездный квантификатор к группе без захвата из двух альтернатив, каждая из которых использует только один или два символа за раз. Это чередование может быть полностью устранено путем признания того, что один и тот же шаблон повторяется снова и снова, и что эквивалентное выражение может быть создано для того же действия без чередования. Вот оптимизированное выражение для соответствия одной строке в кавычках и захвата ее содержимого в группу $1:

# Better regex to match single quoted string:
re_sq_short = r"'([^'\\]*(?:\\.[^'\\]*)*)'"
re_sq_long = r"""
    '            # Literal opening quote
    (            # $1: Contents.
      [^'\\]*    # {normal*} Zero or more non-', non-escapes.
      (?:        # Group for {(special normal*)*} construct.
        \\.      # {special} Escaped anything.
        [^'\\]*  # More {normal*}.
      )*         # Finish up {(special normal*)*} construct.
    )            # End $1: Contents.
    '
    """

Это выражение поглощает все не-кавычки, не-обратные слеши (подавляющее большинство большинства строк) за один "глоток", что резко сокращает объем работы, которую должен выполнять механизм регулярных выражений. Насколько лучше ты спрашиваешь? Итак, я ввел каждое из регулярных выражений, представленных в этом вопросе, в RegexBuddy и измерил, сколько шагов потребовалось механизму регулярных выражений для завершения сопоставления в следующей строке (что все решения правильно сопоставляют):

'This is an example string which contains one \'internally quoted\' string.'

Вот результаты тестов для приведенной выше тестовой строки:

r"""
AUTHOR            SINGLE-QUOTE REGEX   STEPS TO: MATCH  NON-MATCH
Evan Fosmark      '(.*?)(?<!\\)'                  374     376
Douglas Leeder    '(([^\\']|\\'|\\\\)*)'          154     444
cletus/PEZ        '((?:\\'|[^'])*)(?<!\\)'        223     527
MizardX           '((?:\\.|[^\\'])*)'             221     369
MizardX(improved) '((?:[^\\']|\\.)*)'             153     369
Jeffrey Friedl    '([^\\']*(?:\\.[^\\']*)*)'       13      19
"""

Это количество шагов, необходимое для сопоставления тестовой строки с использованием функции отладчика RegexBuddy. Столбец «NON-MATCH» - это количество шагов, необходимых для объявления ошибки совпадения, когда закрывающая кавычка удаляется из тестовой строки. Как видите, разница значима как для совпадающих, так и для несоответствующих случаев. Также обратите внимание, что эти улучшения эффективности применимы только к механизму NFA, который использует возврат (например, Perl, PHP, Java, Python, Javascript, .NET, Ruby и большинство других.) Механизм DFA не увидит никакого повышения производительности с помощью этой техники ( См .: Сопоставление регулярных выражений может быть простым и быстрым ).

На полное решение:

Цель исходного вопроса (моя интерпретация) состоит в том, чтобы выбрать подстроки в одинарных кавычках (которые могут содержать экранированные кавычки) из строки большего размера. Если известно, что текст вне заключенных в кавычки подстрок никогда не будет содержать escape-одиночные кавычки, приведенное выше регулярное выражение выполнит эту работу. Тем не менее, чтобы правильно сопоставить подстроки в одинарных кавычках в море текста, плавающем с кавычками-кавычками и escape-кавычками и escaped-everything-elses (такова моя интерпретация того, что следует за автором), требует анализа с начала строки Нет, (это то, что я изначально думал), но это не так - это может быть достигнуто с помощью очень умного (?<!\\)(?:\\\\)* выражения MizardX. Вот несколько тестовых строк для реализации различных решений:

text01 = r"out1 'escaped-escape:        \\ ' out2"
test02 = r"out1 'escaped-quote:         \' ' out2"
test03 = r"out1 'escaped-anything:      \X ' out2"
test04 = r"out1 'two escaped escapes: \\\\ ' out2"
test05 = r"out1 'escaped-quote at end:   \'' out2"
test06 = r"out1 'escaped-escape at end:  \\' out2"
test07 = r"out1           'str1' out2 'str2' out2"
test08 = r"out1 \'        'str1' out2 'str2' out2"
test09 = r"out1 \\\'      'str1' out2 'str2' out2"
test10 = r"out1 \\        'str1' out2 'str2' out2"
test11 = r"out1 \\\\      'str1' out2 'str2' out2"
test12 = r"out1         \\'str1' out2 'str2' out2"
test13 = r"out1       \\\\'str1' out2 'str2' out2"
test14 = r"out1           'str1''str2''str3' out2"

Принимая во внимание эти тестовые данные, давайте посмотрим, как работают различные решения ('p' == pass, 'XX' == fail):

r"""
AUTHOR/REGEX     01  02  03  04  05  06  07  08  09  10  11  12  13  14
Douglas Leeder    p   p  XX   p   p   p   p   p   p   p   p  XX  XX  XX
  r"(?:^|[^\\])'(([^\\']|\\'|\\\\)*)'"
cletus/PEZ        p   p   p   p   p  XX   p   p   p   p   p  XX  XX  XX
  r"(?<!\\)'((?:\\'|[^'])*)(?<!\\)'"
MizardX           p   p   p   p   p   p   p   p   p   p   p   p   p   p
  r"(?<!\\)(?:\\\\)*'((?:\\.|[^\\'])*)'"
ridgerunner       p   p   p   p   p   p   p   p   p   p   p   p   p   p
  r"(?<!\\)(?:\\\\)*'([^'\\]*(?:\\.[^'\\]*)*)'"
"""

Рабочий тестовый скрипт:

import re
data_list = [
    r"out1 'escaped-escape:        \\ ' out2",
    r"out1 'escaped-quote:         \' ' out2",
    r"out1 'escaped-anything:      \X ' out2",
    r"out1 'two escaped escapes: \\\\ ' out2",
    r"out1 'escaped-quote at end:   \'' out2",
    r"out1 'escaped-escape at end:  \\' out2",
    r"out1           'str1' out2 'str2' out2",
    r"out1 \'        'str1' out2 'str2' out2",
    r"out1 \\\'      'str1' out2 'str2' out2",
    r"out1 \\        'str1' out2 'str2' out2",
    r"out1 \\\\      'str1' out2 'str2' out2",
    r"out1         \\'str1' out2 'str2' out2",
    r"out1       \\\\'str1' out2 'str2' out2",
    r"out1           'str1''str2''str3' out2",
    ]

regex = re.compile(
    r"""(?<!\\)(?:\\\\)*'([^'\\]*(?:\\.[^'\\]*)*)'""",
    re.DOTALL)

data_cnt = 0
for data in data_list:
    data_cnt += 1
    print ("\nData string %d" % (data_cnt))
    m_cnt = 0
    for match in regex.finditer(data):
        m_cnt += 1
        if (match.group(1)):
            print("  quoted sub-string%3d = \"%s\"" %
                (m_cnt, match.group(1)))

Уф!

p.s. Спасибо MizardX за очень классное выражение (?<!\\)(?:\\\\)*. Узнай что-то новое каждый день!

5 голосов
/ 10 января 2009

Я думаю, что это будет работать:

import re
regexc = re.compile(r"(?:^|[^\\])'(([^\\']|\\'|\\\\)*)'")

def check(test, base, target):
    match = regexc.search(base)
    assert match is not None, test+": regex didn't match for "+base
    assert match.group(1) == target, test+": "+target+" not found in "+base
    print "test %s passed"%test

check("Empty","''","")
check("single escape1", r""" Example: 'Foo \' Bar'  End. """,r"Foo \' Bar")
check("single escape2", r"""'\''""",r"\'")
check("double escape",r""" Example2: 'Foo \\' End. """,r"Foo \\")
check("First quote escaped",r"not matched\''a'","a")
check("First quote escaped beginning",r"\''a'","a")

Регулярное выражение r"(?:^|[^\\])'(([^\\']|\\'|\\\\)*)'" является прямым соответствием только тем вещам, которые нам нужны в строке:

  1. Символы, которые не имеют обратной косой черты или цитаты.
  2. Побег цитата
  3. Побег с обратной косой чертой

EDIT:

Добавить дополнительное регулярное выражение в начало, чтобы проверить, не вышла ли первая цитата.

3 голосов
/ 10 января 2009

Шаблон Дугласа Лидера ((?:^|[^\\])'(([^\\']|\\'|\\\\)*)') не будет соответствовать "test 'test \x3F test' test" и "test \\'test' test". (Строка, содержащая escape-код, отличный от кавычек и обратной косой черты, и строка, перед которой стоит экранированный обратный слеш.)

Шаблон

cletus ((?<!\\)'((?:\\'|[^'])*)(?<!\\)') не будет соответствовать "test 'test\\' test". (Строка заканчивается символом обратной косой черты.)

Мое предложение для строк в одинарных кавычках таково:

(?<!\\)(?:\\\\)*'((?:\\.|[^\\'])*)'

Вы можете использовать как одинарные, так и двойные кавычки:

(?<!\\)(?:\\\\)*("|')((?:\\.|(?!\1)[^\\])*)\1

Тестовый запуск с использованием Python:

Doublas Leeder´s test cases:
"''" matched successfully: ""
" Example: 'Foo \' Bar'  End. " matched successfully: "Foo \' Bar"
"'\''" matched successfully: "\'"
" Example2: 'Foo \\' End. " matched successfully: "Foo \\"
"not matched\''a'" matched successfully: "a"
"\''a'" matched successfully: "a"

cletus´ test cases:
"'testing 123'" matched successfully: "testing 123"
"'testing 123\\'" matched successfully: "testing 123\\"
"'testing 123" didn´t match, as exected.
"blah 'testing 123" didn´t match, as exected.
"blah 'testing 123'" matched successfully: "testing 123"
"blah 'testing 123' foo" matched successfully: "testing 123"
"this 'is a \' test'" matched successfully: "is a \' test"
"another \' test 'testing \' 123' \' blah" matched successfully: "testing \' 123"

MizardX´s test cases:
"test 'test \x3F test' test" matched successfully: "test \x3F test"
"test \\'test' test" matched successfully: "test"
"test 'test\\' test" matched successfully: "test\\"
1 голос
/ 10 января 2009

Если я понимаю, что вы говорите (и я не уверен, что понимаю), вы хотите найти строку в кавычках в вашей строке, игнорируя экранированные кавычки. Это правильно? Если это так, попробуйте это:

/(?<!\\)'((?:\\'|[^'])*)(?<!\\)'/

В основном:

  • Начните с одинарной кавычки, которой не предшествует обратная косая черта;
  • Соответствует нулю или большему количеству вхождений: обратной косой черты, затем кавычки или любого другого символа, кроме кавычки;
  • Конец в кавычке;
  • Не группируйте средние скобки (оператор?:); и
  • Закрывающей цитате не может предшествовать обратный слеш.

Хорошо, я проверил это на Java (извините, это скорее мой вопрос, чем Python, но принцип тот же):

private final static String TESTS[] = {
        "'testing 123'",
        "'testing 123\\'",
        "'testing 123",
        "blah 'testing 123",
        "blah 'testing 123'",
        "blah 'testing 123' foo",
        "this 'is a \\' test'",
        "another \\' test 'testing \\' 123' \\' blah"
};

public static void main(String args[]) {
    Pattern p = Pattern.compile("(?<!\\\\)'((?:\\\\'|[^'])*)(?<!\\\\)'");
    for (String test : TESTS) {
        Matcher m = p.matcher(test);
        if (m.find()) {
            System.out.printf("%s => %s%n", test, m.group(1));
        } else {
            System.out.printf("%s doesn't match%n", test);
        }
    }
}

Результаты:

'testing 123' => testing 123
'testing 123\' doesn't match
'testing 123 doesn't match
blah 'testing 123 doesn't match
blah 'testing 123' => testing 123
blah 'testing 123' foo => testing 123
this 'is a \' test' => is a \' test
another \' test 'testing \' 123' \' blah => testing \' 123

что кажется правильным.

0 голосов
/ 27 сентября 2013
>>> print re.findall(r"('([^'\\]|\\'|\\\\)*')",r""" Example: 'Foo \' Bar'  End. """)[0][0]

'Foo \' Bar '

0 голосов
/ 10 января 2009

Использование выражения cletus 'с функцией re.findall () Python:

re.findall(r"(?<!\\)'((?:\\'|[^'])*)(?<!\\)'", s)

Проверка нахождения нескольких совпадений в строке:

>>> re.findall(r"(?<!\\)'((?:\\'|[^'])*)(?<!\\)'",
 r"\''foo bar gazonk' foo 'bar' gazonk 'foo \'bar\' gazonk' 'gazonk bar foo\'")
['foo bar gazonk', 'bar', "foo \\'bar\\' gazonk"]
>>>

Использование массива строк TETTS в cletus:

["%s => %s" % (s, re.findall(r"(?<!\\)'((?:\\'|[^'])*)(?<!\\)'", s)) for s in TESTS]

Работает как шарм. (Проверь это сам или поверь мне на слово.)

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