Parsec,读取文本以字符串结尾

时间:2013-12-22 14:33:08

标签: haskell parsec blaze-html

我正在努力与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 *",存在代码复制(这有点痛苦),用于识别文本序列的结尾,列表项的开头和列表项的结尾...

2 个答案:

答案 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更清洁:)