我正在努力与Parsec
解析Google project wiki syntax的一小部分,并将其转换为HTML。我的语法仅限于文本序列和项目列表。以下是我想要识别的一个例子:
Text that can contain any kind of characters,
except the string "\n *"
* list item 1
* list item 2
End of list
到目前为止我的代码是:
import Text.Blaze.Html5 (Html, toHtml)
import qualified Text.Blaze.Html5 as H
import Text.ParserCombinators.Parsec hiding (spaces)
parseList :: Parser Html
parseList = do
items <- many1 parseItem
return $ H.ul $ sequence_ items
parseItem :: Parser Html
parseItem = do
string "\n *"
item <- manyTill anyChar $
(try $ lookAhead $ string "\n *") <|>
(try $ string "\n\n")
return $ H.li $ toHtml item
parseText :: Parser Html
parseText = do
text <- manyTill anyChar $
(try $ lookAhead $ string "\n *") <|>
(eof >> (string ""))
return $ toHtml text
parseAll :: Parser Html
parseAll = do
l <- many (parseUl <|> parseText)
return $ H.html $ sequence_ l
将parseAll
应用于任何字符序列时,会收到以下错误消息:"*** Exception: Text.ParserCombinators.Parsec.Prim.many: combinator 'many' is applied to a parser that accepts an empty string.
我理解这是因为我的解析器parseText
可以读取空字符串,但我看不到任何其他方式。如何识别字符串分隔的文本? ("\n *"
此处)。
我也对有关我使用Parsec的方式的任何评论或建议持开放态度。我不禁看到我的代码有点难看。我可以用更简单的方式完成所有这些吗?例如,由于字符串"\n *"
,存在代码复制(这有点痛苦),用于识别文本序列的结尾,列表项的开头和列表项的结尾...
答案 0 :(得分:2)
问题是manyTill
组合子匹配零个或多个anyChar。只需更改parseText以匹配至少一个anyChar,以便在读取其中一个分隔符时失败 - 遗憾的是没有many1Till
组合子。
我也更喜欢parseAll = fmap (H.html . sequence) $ many (parseUl <|> parseText)
,因为你提到了丑陋的提示。
parseText = do
notFollowedBy $ string "\n *"
first <- anyChar
rest <- manyTill anyChar $
(try $ lookAhead $ string "\n *") <|>
(eof >> (string ""))
return $ toHtml first:rest
parseAll = fmap (H.html . sequence) $ many (parseUl <|> parseText)
那就是说,google上的“parseUl”只给出了这个问题,所以如果不理解解析器,我就不知道更好的解决方案。
绝望我的第一个接受的答案,我全部写完:)只需在fmap(首选)上添加html内容或返回。
module Main where
import System.Environment
import Control.Monad
import Text.ParserCombinators.Parsec hiding (spaces)
parseList :: Parser [String]
parseList = many1 parseItem
parseItem :: Parser String
parseItem = string "\n *" >> (manyTill anyChar $ try $ lookAhead $ char '\n')
parseText :: Parser String
parseText = do
notFollowedBy $ string "\n *"
first <- anyChar
rest <- manyTill anyChar $
(try $ lookAhead $ string "\n *") <|>
(eof >> (string ""))
return $ first:rest
parseAll :: Parser [String]
parseAll = many $ parseText <|> fmap concat parseList
parseIt :: String -> String
parseIt input = case parse parseAll "wiki" input of
Left err -> "No match: " ++ show err
Right val -> "It worked"
main = do
args <- getArgs
putStrLn (parseIt (args !! 0))
我假设列表不能包含换行符,但try $ lookahead $ char '\n'
很容易调整。您可以将string "\n *"
分解出来以避免重复。在这里,我粉碎了所有列表并忽略了序列的解析,但你必须改变它。如果将“文本”划分为文本行,然后只检查文本行或列表中的行,则会更简单。
答案 1 :(得分:1)
parseItem :: Parser String
parseItem = do
manyTill anyChar $
(try $ lookAhead $ string "\n *") <|>
(try $ string "\n\n")
parseText :: Parser [String]
parseText =
string "\n *" >> -- remove this if text *can't* contain a leading '\n *'
sepBy1 parseItem (string "\n *")
我删除了HTML内容,因为无论出于何种原因我都无法在我的计算机上安装blaze-html
。但原则上它本质上应该是一样的。这解析由字符串“\ n *”分隔的字符串,并以字符串“\ n \ n”结束。我不知道是否有一个领先的\n
是你想要的,但这很容易解决。
另外,我不知道空字符串是否有效。如果是,请将sepBy1
更改为sepBy
。
至于你得到的错误:string ""
内有many
。这不仅会给你带来错误,也没有任何意义!解析器string ""
将始终成功而不消耗任何东西,因为空字符串是所有字符串的前缀"" ++ x == x
。如果你多次尝试这样做,那么你永远不会完成解析。
除此之外,您的parseList
应该解析您的语言。它基本上与sepBy
做同样的事情。我只是认为sepBy
更清洁:)