Pyparsing:嵌套的Markdown强调

3
我正在尝试使用一些简单的Markdown文本进行玩耍,并学习Pyparsing和语法。但我立刻遇到了一个问题,我很难解决。我正在尝试解析强调的CommonMark规范的简单版本。在这种设置中,允许嵌套强调,因此
*foo *bar* baz*

应该给出:

<em>foo <em>bar</em> baz</em>

我尝试使用递归定义来匹配这个,但是它没有起作用。以下是一些示例代码:
from pyparsing import *

text = Word(printables,excludeChars="*")
enclosed = Forward()
emphasis = QuotedString("*").setParseAction(lambda x: "<em>%s</em>" % x[0],contents=enclosed)
enclosed << emphasis | text

test = """
*foo *bar* bar*
"""

print emphasis.transformString(test)

但我从中得到的是:
<em>foo </em>bar<em> bar</em>

请原谅我的新手问题; 有人能给我指个方向吗?

编辑:

针对abarnert的深入问题,我将提供澄清。 我只是在玩耍,所以可以使用任意限制形式的符号。 我假设只出现单个'*',并且它们不会相互靠近。 这就留下了空格来消除歧义:*后面没有空格表示打开强调,*前面没有空格表示关闭强调。

即使如此,我也不确定如何使用Pyparsing继续下去。 一些基于堆栈的方法,当它们验证为关闭时,推动打开*并将其弹出? 如何使用Pyparsing做到这一点? 还是有更有效的方法吗?


我认为实现你想要的版本最简单的方法是放弃自动处理空白字符,将空白字符视为一个标记...但我不确定。我会让我的潜意识仔细考虑一下,如果你仍然遇到困难而且没有其他人回复你,我稍后会尝试解决它。 - abarnert
一旦你说出像“将空格视为标记”这样的话,我认为pyparsing就不是最佳选择了。隐式的跳过空格是其魅力的关键部分。 - PaulMcG
2个回答

2

有了这些额外的规则,我认为你不需要担心递归问题,只需按照它们被发现的方式处理开放和关闭强调表达式,无论它们是否匹配:

from pyparsing import *

openEmphasis = (LineStart() | White()) + Suppress('*')
openEmphasis.setParseAction(lambda x: ''.join(x.asList()+['<em>']))
closeEmphasis = '*' + FollowedBy(White() | LineEnd())
closeEmphasis.setParseAction(lambda x: '</em>')

emphasis = (openEmphasis | closeEmphasis).leaveWhitespace()

test = """
*foo *bar* bar*
"""
print test
print emphasis.transformString(test)

输出:

*foo *bar* bar*

<em>foo <em>bar</em> bar</em>

你不是第一个遇到这种应用的人。当我在PyCon'06上做演讲时,一个热心的听众试图解析一些markdown,输入字符串类似于"****a** b**** c**"之类的内容。我们一起努力了一段时间,但消歧规则对于基本的pyparsing解析器来说太过上下文感知。


非常感谢,保罗!我很钦佩你在Pyparsing方面给予人们的帮助。出于好奇,您认为Pyparsing能否为Commonmark生成完整的解析器?(我本来不打算尝试,只是想通过该库和语法来入门,但这让我想知道是否可能)。 - Winawer
跳过递归意味着如果给定损坏的Markdown,他将成功生成损坏的HTML,而不是失败,对吗?(我认为这不是问题 - 他从未要求编写验证解析器或生成带有强调节点或任何内容的树 - 我只想澄清一下。) - abarnert
@Winawer:如果你遵循解析策略,它看起来应该是可行的——也就是说,首先进行第一次遍历以构建树,然后进行树遍历。在一些章节中,规范提到了一些可以无需回溯解析的内容,这听起来像是他们确保设计了一个LL(k)语言(如果你没有区分预测性递归下降和回溯递归下降,你不会谈论回溯,对吧?)。 - abarnert
快速查看规范就会发现pyparsing的克星:显著的前导空格(用于识别代码块)。Pyparsing在处理将输入文本作为连续流或一系列单独行的应用程序时效果更好。 - PaulMcG
@abarnert - 是的,糟糕的Markdown会导致使用这种方法生成糟糕的HTML。我看到的许多pyparsing应用程序都做出了这个假设,即输入已经被单独验证过,并且pyparsing应用程序正在提取一些感兴趣的部分。我现在正在使用一大块Groovy代码来做这件事-我知道它在语法上是正确的,所以我可以只筛选类定义等内容,而不必担心错误的语法。 - PaulMcG
@PaulMcGuire:实际上,解析策略部分明确建议将其解析为一系列独立的行。然而,你似乎需要跟踪缩进上下文以处理许多产生式(列表项看起来比代码块更棘手)。显然,你比我更清楚这是否可行。 - abarnert

2
考虑一下你所要求的。第二个*何时结束强调,何时开启嵌套强调?你没有写出区分它们的规则。由于它始终是100%不明确的,这意味着你可能得到以下两种结果之一:
  • 永远没有强调可以关闭,或
  • 永远没有强调可以嵌套。
我怀疑你并不想问如何从第二个结果切换到第一个结果。
那么你到底在问什么呢?
你需要实现某种规则来消除这两种可能性的歧义。
事实上,如果你阅读了链接到的文档,你会发现它们有一套复杂的规则,用于定义*何时可以开启强调,何时不能,同样适用于关闭;在给定这些规则的情况下,如果仍然存在歧义,它将关闭强调。你必须实现这些规则。

感谢您促使我澄清思路。我希望我对问题的编辑有所帮助。 - Winawer
非常好的评论。这就是为什么我鼓励人们在开始“编写解析器”之前,先通过撰写粗略的BNF来思考语法的原因。 - PaulMcG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接