2

我正在努力解析Google 项目 wiki 语法Parsec的一小部分,并将其转换为 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 的方式的任何评论或建议。我不禁看到我的代码有点难看。我能以更简单的方式完成这一切吗?例如,由于 string 存在代码复制(这有点痛苦)"\n *",用于识别文本序列的结尾、列表项的开头和列表项的结尾......

4

2 回答 2

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)

也就是说,谷歌上的“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 *"以避免重复。在这里,我粉碎了所有列表并忽略了带有序列的解析,但你必须改变它。如果您将“文本”分成多行文本,然后只检查一行文本或列表中的一行,这一切都会更简单。

于 2013-12-23T01:24:58.537 回答
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是你想要的,但这很容易解决。

另外,我不知道空字符串是否有效。如果是,您应该更改sepBy1sepBy

至于你得到的错误:你string ""many. 这不仅会给出你得到的错误,而且没有任何意义!解析器string ""总是会成功而不消耗任何东西,因为空字符串是所有字符串的前缀,并且"" ++ x == x. 如果您尝试多次执行此操作,那么您将永远无法完成解析。

除此之外,您还parseList应该解析您的语言。它基本上做同样的事情sepBy。我只是觉得sepBy更干净:)

于 2013-12-22T16:12:06.203 回答