我正在努力使用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
应用于任何字符序列时,我收到以下错误消息:"*** 异常:Text.ParserCombinators.Parsec.Prim.many:组合器 'many' 是应用于接受空字符串的解析器。
我明白这是因为我的解析器 parseText
可以读取空字符串,但我看不到任何其他方式。如何识别由字符串分隔的文本? (此处为“\n *”
)。
我也愿意接受有关我使用秒差距的方式的任何评论或建议。我不禁发现我的代码有点难看。我可以用更简单的方式完成这一切吗?例如,由于字符串 "\n *"
,存在代码复制(这有点痛苦),该字符串用于识别文本序列的结尾、列表项的开头,以及列表项的末尾...
最佳答案
问题是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)
也就是说,谷歌上的“parseUl”只给出了这个问题,所以如果不了解该解析器,我不知道更好的解决方案。
由于我对第一个接受的答案感到绝望,我完整地写了它:)只需在上面添加 html 内容与 fmap (首选)或 return 即可。
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 *"
以避免重复。在这里,我粉碎了所有列表并忽略了序列解析,但您必须更改它。如果您将“文本”分成多行文本,然后仅检查一行文本或列表中的一行,那么一切都会更简单。
关于haskell - Parsec,读取以字符串结尾的文本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20730488/