【问题标题】:With pyparsing, how do you parse a quoted string that ends with a backslash使用pyparsing,如何解析以反斜杠结尾的带引号的字符串
【发布时间】:2014-04-26 02:09:23
【问题描述】:

我正在尝试使用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)

【问题讨论】:

  • 您希望如何解析示例文本?对我来说,这似乎是一个格式错误的引号字符串,因为只有一个未转义的引号。
  • 我不确定这是可能的(或至少是实用的)。你打算如何区分ab\'cd\'ab\'cd\' <100MB of other text> \'?也就是说,您如何在上下文中知道转义的引号是否是字符串的结尾?
  • @Blckknght 我希望以我的正则表达式解析它的方式解析它。
  • @BrenBarn 也许我忽略了一些明显的东西......但是 100MB 的其他文本有什么关系?例如:txt = r"ab\'cd\' <100MB of other text> \'"时,我的rgx解析文本就好了。 pyparsing 不应该能够对QuotedString 对象做类似的事情吗?
  • @FMc:看我的回答。基本上,如果你知道你只解析一个字符串,你的正则表达式就可以了。但是,当您实际尝试解析可能包含许多此类分隔字符串的整个文档时,您的方法可能会导致解析混乱。

标签: python regex pyparsing quoting


【解决方案1】:

我认为您误解了escQuote 的用法。根据the docs

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'

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

虽然我目前无法提供示例,但我也怀疑如果您实际尝试解析包含多个此类字符串的相当大的文档,这可能会导致“灾难性回溯”。 (这是我关于“100MB 其他文本”的观点。)因为解析器无法知道给定的\' 是否是字符串的结尾而不进一步解析,它可能必须一直走到结尾文件只是为了确保那里没有更多的引号。如果文件的剩余部分包含这种类型的附加字符串,那么确定哪些引号分隔哪些字符串可能会变得复杂。例如,如果输入包含类似

'one string \' 'or two'

我们无法判断这是两个有效字符串(one string \or two)还是一个后面有无效材料的字符串(one string \' 和非字符串标记 or two 后跟一个不匹配的引号)。这种情况在很多解析上下文中是不可取的;您希望关于字符串在哪里开始和结束的决定是本地可确定的,而不是依赖于文档中稍后出现的其他标记。

【讨论】:

  • 完全符合escQuote 的目的。我实现了这个参数来解析 SQL 中的字符串,其中引号不是用反斜杠转义的,而是加倍引号(如"This is "" an embedded quote char")。
【解决方案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 的下一个补丁版本中添加这个。

【讨论】:

  • 我需要以下结果:['foo', '=', "ab'cd\\"].
  • 感谢您对此进行调查,以及对 pyparsing 的一般性工作。我已经使用了很多,并从中学到了很多东西。
【解决方案3】:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-12
    • 2016-05-10
    • 2011-11-14
    相关资源
    最近更新 更多