Parsec 忽略除一个片段之外的所有内容

Parsec ignore everything except one fragment

我需要解析格式不正确的 HTML 文档中的单个 select 标记(因此基于 XML 的解析器不起作用)。

我想我知道如何在到达那里后使用 parsec 来解析 select 标记,但是如何跳过该标记前后的所有内容?

示例:

<html>
   random content with lots of tags...
   <select id=something title="whatever"><option value=1 selected>1. First<option value=2>2. Second</select>
   more random content...
</html>

这实际上就是 HTML 在 select 标签中的样子。我如何使用 Parsec 执行此操作,或者您会推荐我使用其他库吗?

这是我的做法:

solution = (do {
  ; string "<tag-name"
  ; x <- ⟦insertOptionsParserHere⟧
  ; char '>'
  ; return x
  }) <|> (anyChar >> solution)

这将递归地使用字符,直到遇到起始 <html> 标记,然后使用您的解析器,并在使用最终标记时离开递归。

明智的做法是注意前后可能有尾随空格要解决这个问题,我们可以这样做,前提是您的解析器使用标签:

solution = ⟦insertHtmlParserHere⟧ <|> (anyChar >> solution)

要清楚这意味着 ⟦insertHtmlParserHere⟧ 将具有这种结构:

⟦insertHtmlParserHere⟧ = do
   string "<tag-name"
   ⋯
   char '>'

附带说明一下,如果您想捕获每个可用的标签,您可以非常愉快地使用 many:

everyTag = many solution

您可以尝试使用正则表达式并捕获 select 标签:

import Text.ParserCombinators.Parsec
import Text.Regex.Posix


getOptionTags content = content =~ "(<select.*</select>)"::[[String]]

main :: IO ()
main = do
    s <- readFile "in"
    putStrLn . show . head . head $ getOptionTags s

您可以使用 Replace.Megaparsec.findAll 在文档中查找与解析器匹配的子字符串。

import Replace.Megaparsec
import Text.Megaparsec

let parseSelect :: Parsec Void String String
    parseSelect = do
        chunk "<select"
        manyTill anySingle $ chunk "</select>"
let input = "<html>\n   random content with lots of tags...\n   <select id=something title=\"whatever\"><option value=1 selected>1. First<option value=2>2. Second</select>\n   more random content...\n</html>"
>>> parseTest (findAll parseSelect) input
[Left "<html>\n   random content with lots of tags...\n   "
,Right "<select id=something title=\"whatever\"><option value=1 selected>1. First<option value=2>2. Second</select>"
,Left "\n   more random content...\n</html>"
]