使用pyparsing,如何解析以反斜杠结尾的带引号的字符串

时间:2014-04-26 02:09:23

标签: python regex pyparsing quoting

我正在尝试使用pyparsing在以下条件下解析引用的字符串:

  • 引用的字符串可能包含内部引号。
  • 我想使用反斜杠来逃避内部引号。
  • 引用的字符串可能以反斜杠结尾。

我很难定义一个成功的解析器。另外,我开始怀疑pyparsing用于引用这种字符串的正则表达式是否正确(请参阅下面的替代正则表达式)。

我是否错误地使用了pyparsing(最有可能)或者pyparsing中是否存在错误?

这是一个演示问题的脚本(注意:忽略此脚本;请关注以下更新。):

import pyparsing as pp
import re

# A single-quoted string having:
#   - Internal escaped quote.
#   - A backslash as the last character before the final quote.
txt = r"'ab\'cd\'"

# Parse with pyparsing.
# Does not work as expected: grabs only first 3 characters.
parser = pp.QuotedString(quoteChar = "'", escChar = '\\', escQuote = '\\')
toks   = parser.parseString(txt)
print
print 'txt:    ', txt
print 'pattern:', parser.pattern
print 'toks:   ', toks

# Parse with a regex just like the pyparsing pattern, but with
# the last two groups flipped -- which seems more correct to me.
# This works.
rgx = re.compile(r"\'(?:[^'\n\r\\]|(?:\\.)|(?:\\))*\'")
print
print rgx.search(txt).group(0)

输出:

txt:     'ab\'cd\'
pattern: \'(?:[^'\n\r\\]|(?:\\)|(?:\\.))*\'
toks:    ["ab'"]

'ab\'cd\'

更新

感谢您的回复。我怀疑我把问题弄得很糟糕,所以让我再试一次。

假设我们正在尝试解析一种使用通常类似于Python的引用规则的语言。我们希望用户能够定义可以包含内部引号的字符串(由反斜杠保护),并且我们希望这些字符串能够以反斜杠结尾。这是我们语言的示例文件。请注意,该文件也将解析为有效的Python语法,如果我们打印foo(在Python中),则输出将是文字值:ab'cd\

# demo.txt
foo = 'ab\'cd\\'

我的目标是使用pyparsing来解析这种语言。有办法吗?上面的问题基本上是在几次尝试失败之后我最终的结果。以下是我最初的尝试。它失败了,因为最后有两个反斜杠,而不是一个。

with open('demo.txt') as fh:
    txt = fh.read().split()[-1].strip()

parser = pp.QuotedString(quoteChar = "'", escChar = '\\')
toks   = parser.parseString(txt)
print
print 'txt:    ', txt
print 'pattern:', parser.pattern
print 'toks:   ', toks             # ["ab'cd\\\\"]

我想问题是QuotedString仅将反斜杠视为引用转义,而Python则将反斜杠视为更通用的转义。

有一种简单的方法可以做到这一点,我忽略了吗?我遇到的一个解决方法是使用.setParseAction(...)来处理事后的双反斜杠 - 也许是这样,这似乎有效:

qHandler = lambda s,l,t: [ t[0].replace('\\\\', '\\') ]
parser = pp.QuotedString(quoteChar = "'", escChar = '\\').setParseAction(qHandler)

3 个答案:

答案 0 :(得分:3)

我认为你误解了escQuote的使用。根据{{​​3}}:

  

escQuote - 用于转义嵌入式引号字符串的特殊引号序列(例如SQL的“”以转义嵌入式“)(默认=无)

所以escQuote用于指定一个被解析为文字引号的完整序列。例如,在文档中给出的示例中,您将指定escQuote='""',并将其解析为"。通过将反斜杠指定为escQuote,可以将单个反斜杠解释为引号。你没有在你的例子中看到这个,因为除了引号之外你没有转义任何东西。但是,如果你试图逃避其他事情,你会发现它不起作用:

>>> txt = r"'a\Bc'"
>>> parser = pyp.QuotedString(quoteChar = "'", escChar = '\\', escQuote = "\\")
>>> parser.parseString(txt)
(["a'Bc"], {})

请注意,反斜杠已替换为'

至于你的替代方案,我认为pyparsing(和许多其他解析器)不这样做的原因是它涉及字符串中的特殊外壳一个位置。在你的正则表达式中,单个反斜杠是一个转义字符,除了作为字符串中的最后一个字符,在字面上处理它的位置。这意味着你不能“本地”告诉某个给定的引号是否真的是字符串的结尾 - 即使它有一个反斜杠,如果没有一个反斜杠,它可能不会结束。这可能导致解析模糊和令人惊讶的解析行为。例如,请考虑以下示例:

>>> txt = r"'ab\'xxxxxxx"
>>> print rgx.search(txt).group(0)
'ab\'
>>> txt = r"'ab\'xxxxxxx'"
>>> print rgx.search(txt).group(0)
'ab\'xxxxxxx'

通过在字符串的末尾添加一个撇号,我突然导致较早的撇号不再是结束,并立即将所有x添加到字符串中。在实际使用上下文中,这可能导致令人困惑的情况,其中不匹配的引号静默地导致重新分析字符串而不是解析错误。

虽然我目前无法提出一个例子,但我也怀疑如果你真的试图解析包含这种类型的多个字符串的相当大的文档,这有可能导致“灾难性的回溯”。 (这是我对“100MB其他文本”的看法。)因为解析器无法知道给定的\'是否是字符串的结尾而没有进一步解析,它可能不得不一直走到文件的结尾只是为了确保没有更多的引号。如果文件的剩余部分包含此类型的其他字符串,则可能会弄清楚哪些引号正在分隔哪些字符串。例如,如果输入包含类似

的内容
'one string \' 'or two'

我们无法判断这是两个有效字符串(one string \or two)还是其后包含无效字符的字符串one string \'和非字符串标记{{1}然后是一个无与伦比的报价)。在许多解析上下文中,这种情况是不可取的;你想要关于字符串开始和结束的位置可以在本地确定的决定,而不是在文档的后期更多地依赖于其他标记的出现。

答案 1 :(得分:2)

PyParsing的QuotedString解析器不处理以反斜杠结尾的带引号的字符串。这是一个基本的限制,没有任何简单的解决方法,我可以看到。如果您想支持这种字符串,则需要使用QuotedString以外的其他内容。

这也不是一个不常见的限制。 Python本身不允许在“原始”字符串文字的末尾使用奇数个反斜杠。试一试:r"foo\"会引发异常,而r"bar\\"会在输出中包含两个反斜杠。

您从当前代码中获取截断输出(而不是异常)的原因是因为您将反斜杠作为escQuote参数传递。我认为这是一个替代指定转义字符,而不是补充。发生的事情是第一个反斜杠被解释为一个内部引用(它无法解释),并且由于它后跟一个实际引用字符,解析器认为它已到达引用字符串的末尾。因此,您得到ab'作为结果。

答案 2 :(得分:2)

这段代码对你不起作用是什么?

from pyparsing import *

s = r"foo = 'ab\'cd\\'"  # <--- IMPORTANT - use a raw string literal here

ident = Word(alphas)
strValue = QuotedString("'", escChar='\\')
strAssign = ident + '=' + strValue

results = strAssign.parseString(s)
print results.asList() # displays repr form of each element

for r in results:
    print r # displays str form of each element

# count the backslashes
backslash = '\\'
print results[-1].count(backslash)

打印:

['foo', '=', "ab'cd\\\\"]
foo
=
ab'cd\\
2

修改

所以“\”“变得只是”'“,但”\“被解析但保持为”\“而不是转义为”\“。看起来像QuotedString中的一个错误。现在您可以添加此解决方法:

import re
strValue.setParseAction(lambda t: re.sub(r'\\(.)', r'\g<1>', t[0]))

将使用每个转义的字符序列,然后单独返回转义字符,而不使用前导'\'。

我将在pyparsing的下一个补丁版本中添加它。