markdown
格式的解析器,以及它的一个子集。
我的问题在于blockquote功能。 blockquote中的每一行都以>
开头;否则一切都是降价文件中的正常结构。
您无法单独查看各行,因为您需要将段落与常规行分开,例如
> a
> b
与
不同> a
>
> b
和类似的东西(如果列表是块引用,你不想要x列表,但是一个列表包含x个元素)。一种自然而琐碎的方式是“起飞”#34; >
符号,自己解析块引用,忽略它周围的任何东西,用BlockQuote
类型构造函数包装它,将它放在外部AST中并继续解析原始输入。如果我没错,pango
的作用是什么:
blockQuote :: MarkdownParser (F Blocks)
blockQuote = do
raw <- emailBlockQuote
-- parse the extracted block, which may contain various block elements:
contents <- parseFromString parseBlocks $ (intercalate "\n" raw) ++ "\n\n"
return $ B.blockQuote <$> contents
然后:
http://hackage.haskell.org/package/pandoc-1.5.1/docs/src/Text-Pandoc-Shared.html#parseFromString
-- | Parse contents of 'str' using 'parser' and return result.
parseFromString :: GenParser tok st a -> [tok] -> GenParser tok st a
parseFromString parser str = do
oldPos <- getPosition
oldInput <- getInput
setInput str
result <- parser
setInput oldInput
setPosition oldPos
return result
现在parseFromString
对我来说非常讨厌,除此之外它还Parsec
而不是attoparsec
所以我无法在我的项目中使用它。我不确定如何从blockquote中取出Text
并解析它并返回解析结果,以便它适合&#34;在当前解析中。似乎不可能?
我一直在谷歌搜索这个问题而我认为 pipes-parse
和conduit
可以帮助该领域,尽管我很难找到例子和我看到的看起来不那么好看&#34;纯粹&#34; parsec / attoparsec解析器。
解析blockquotes的其他选项是重写通常的解析器,但使用>
catch ...复杂和重复很多。解析块引用分别计算每一行并写出一些杂乱的&#34; merge&#34;功能。或者在第一个Text
类型构造函数中解析包含块引用的第一个AST作为BlockquoteText
,等待转换,它们将被单独解析,不是很优雅,但它具有简单性的优点,确实有所作为。
我可能会选择后者,但肯定有更好的方法吗?
答案 0 :(得分:2)
我问自己同样的问题。为什么没有标准的组合子用于嵌套解析器,就像你描述的那样?我的默认模式是信任包作者,特别是当该作者也共同编写了“Real World Haskell”时。如果缺少这样一个明显的能力,也许它是设计的,我应该寻找更好的方法。然而,我已经设法让自己相信这样一个方便的组合器几乎是无害的。每当全有或全无类型解析器适用于内部解析时都很有用。
import Data.Attoparsec.Text
import qualified Data.Text as T
import Data.Text(Text)
import Control.Applicative
我已将所需的功能划分为两个解析器。第一个constP
执行某些给定文本的“就地”解析。它用empty
(来自Alternative)替换常量解析器的失败,但是没有其他副作用。
constP :: Parser a -> Text -> Parser a
constP p t = case parseOnly p t of
Left _ -> empty
Right a -> return a
第二部分来自parseOf
,它根据外部解析的结果执行常量内部解析。这里的empty
替代方案允许失败的解析返回而不消耗任何输入。
parseOf :: Parser Text -> Parser a -> Parser a
parseOf ptxt pa = bothParse <|> empty
where
bothParse = ptxt >>= constP pa
块报价降价可以以所需的方式书写。此实现要求对结果块进行完全解析。
blockQuoteMarkdown :: Parser [[Double]]
blockQuoteMarkdown = parseOf blockQuote ( markdownSurrogate <*
endOfInput
)
我只是实现了一个空格分隔双精度快速解析器,而不是实际的降价解析器。解析器的复杂性来自允许最后一条非空行,无论是否以新行结束。
markdownSurrogate :: Parser [[Double]]
markdownSurrogate = do
lns <- many (mdLine <* endOfLine)
option lns ((lns ++) . pure <$> mdLine1)
where
mdLine = sepBy double (satisfy (==' '))
mdLine1 = sepBy1 double (satisfy (==' '))
这两个解析器负责将文本内部返回到块引号。
blockQuote :: Parser Text
blockQuote = T.unlines <$> many blockLine
blockLine :: Parser Text
blockLine = char '>' *> takeTill isEndOfLine <* endOfLine
最后,对解析器进行测试。
parseMain :: IO ()
parseMain = do
putStrLn ""
doParse "a" markdownSurrogate a
doParse "_" markdownSurrogate ""
doParse "b" markdownSurrogate b
doParse "ab" markdownSurrogate ab
doParse "a_b" markdownSurrogate a_b
doParse "badMarkdown x" markdownSurrogate x
doParse "badMarkdown axb" markdownSurrogate axb
putStrLn ""
doParse "BlockQuote ab" blockQuoteMarkdown $ toBlockQuote ab
doParse "BlockQuote a_b" blockQuoteMarkdown $ toBlockQuote a_b
doParse "BlockQuote axb" blockQuoteMarkdown $ toBlockQuote axb
where
a = "7 3 1"
b = "4 4 4"
x = "a b c"
ab = T.unlines [a,b]
a_b = T.unlines [a,"",b]
axb = T.unlines [a,x,b]
doParse desc p str = do
print $ T.concat ["Parsing ",desc,": \"",str,"\""]
let i = parse (p <* endOfInput ) str
print $ feed i ""
toBlockQuote = T.unlines
. map (T.cons '>')
. T.lines
*Main> parseMain
"Parsing a: \"7 3 1\""
Done "" [[7.0,3.0,1.0]]
"Parsing _: \"\""
Done "" []
"Parsing b: \"4 4 4\""
Done "" [[4.0,4.0,4.0]]
"Parsing ab: \"7 3 1\n4 4 4\n\""
Done "" [[7.0,3.0,1.0],[4.0,4.0,4.0]]
"Parsing a_b: \"7 3 1\n\n4 4 4\n\""
Done "" [[7.0,3.0,1.0],[],[4.0,4.0,4.0]]
"Parsing badMarkdown x: \"a b c\""
Fail "a b c" [] "endOfInput"
"Parsing badMarkdown axb: \"7 3 1\na b c\n4 4 4\n\""
Fail "a b c\n4 4 4\n" [] "endOfInput"
"Parsing BlockQuote ab: \">7 3 1\n>4 4 4\n\""
Done "" [[7.0,3.0,1.0],[4.0,4.0,4.0]]
"Parsing BlockQuote a_b: \">7 3 1\n>\n>4 4 4\n\""
Done "" [[7.0,3.0,1.0],[],[4.0,4.0,4.0]]
"Parsing BlockQuote axb: \">7 3 1\n>a b c\n>4 4 4\n\""
Fail ">7 3 1\n>a b c\n>4 4 4\n" [] "Failed reading: empty"
显着的差异在于失败的语义。例如,在分析axb
和阻止引用axb
时,分别是以下两个字符串
7 3 1
a b c
4 4 4
和
> 7 3 1
> a b c
> 4 4 4
降价解析导致
Fail "a b c\n4 4 4\n" [] "endOfInput"
而引用的结果是
Fail ">7 3 1\n>a b c\n>4 4 4\n" [] "Failed reading: empty"
降价消耗“7 3 1 \ n”,但在引用的失败中没有报道。相反,失败变得全有或全无。
同样,在部分成功的情况下,不允许处理未解析的文本。但考虑到用例,我认为不需要这样做。例如,如果解析看起来像以下
"{ <tok> unhandled }more to parse"
其中{}
表示已识别的块引用上下文,并在该内部上下文中解析<tok>
。然后,部分成功必须将“未处理”提升出该块报价上下文,并以某种方式将其与“更多解析”相结合。
我没有看到通用方法,但是通过选择内部解析器返回类型是允许的。例如,通过一些解析器parseOf blockP innP :: Parser (<tok>,Maybe Text)
。但是,如果出现这种需要,我希望有一种比嵌套解析器更好的方法来处理这种情况。
可能还有人担心attoparsec部分解析的丢失。也就是说,constP
的实现使用parseOnly
,它将解析返回Fail
和Partial
折叠为单个Left
失败状态。换句话说,我们失去了为内部解析器提供更多文本的能力。但是,请注意要解析的文本本身是外部解析的结果;只有在将足够的文本提供给外部解析后才能使用它。所以这不应该是一个问题。