繁体   English   中英

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

[英]With pyparsing, how do you parse a quoted string that ends with a backslash

我正在尝试使用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 QuotedString反斜杠视为更通用的转义。

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

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

我认为你误解了escQuote的使用。 根据文件

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 \\'后面的无效one string \\'one string \\'和非字符串标记or two后跟一个不匹配的引号)。 在许多解析上下文中,这种情况是不可取的; 您希望关于字符串开始和结束的位置可在本地确定的决定,而不是在文档后期更多地依赖于其他标记的出现。

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

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

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

这段代码不适合你的是什么?

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的补丁版本中添加它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM