使用Text.XML.Cursor获取特定的HTML表格列

时间:2017-05-27 02:35:18

标签: html parsing haskell xpath xml-parsing

我正在尝试使用Haskell和Text.XML.Cursor包解析HTML页面。我的目标是返回列表中的第三列值。我花了4个小时试图让它发挥作用。但是,似乎我无法理解XPath和Text.XML.Cursor在概念上如何运作。

所以任务是:

  1. 在文档中的某处找到<table class="forumTable">标记
  2. 对于其中的每个<tr>代码,请使用第三个 <td>代码
  3. 在单元格内部有一个<a>标记,content我要将其添加到列表中。不是href属性,而是<a></a>
  4. 之间的值

    具体来说,我正在解析this link,我试图获取具有论坛名称的列的值(在主题之后的下一个)。

    这是我能得到的。似乎要返回表格中<a>个标签的所有内容

    findNodes :: Cursor -> [Cursor]
    findNodes = element "table" >=> attributeIs "class" "forumTable" &// element "tr" &/ element "td" &/ element "a" >=> child
    extractData = T.concat . content
    ...
    let cursor = fromDocument $ parseLBS $ simpleHTTP "http://www.sql.ru/forum/sqlru-3-days/2"
    let lines = cursor $// findNodes &| extractData
    

    我正在寻找解决我的问题的方法,以及解释这一切是如何运作的。谢谢。

1 个答案:

答案 0 :(得分:2)

我已经明白了!

考虑以下输入文件:

<html>
  <head>
    <title>Haskell XML Parsing Example</title>
  </head>
  <body>
    <table class="forumTable">
      <tbody>
        <tr>
          <td> <a href="#1">1</a> </td>
          <td> <a href="#2">2</a> </td>
          <td> <a href="#3">3</a> </td>
          <td> <a href="#4">4</a> </td>
        </tr>
        <tr>
          <td> <a href="#5">5</a> </td>
          <td> <a href="#6">6</a> </td>
        </tr>
        <tr>
          <td> <a href="#7">7</a> </td>
          <td> <a href="#8">8</a> </td>
          <td> <a href="#9">9</a> </td>
          <td> <a href="#10">10</a> </td>
          <td> <a href="#11">11</a> </td>
        </tr>
      </tbody>
    </table>
  </body>
</html>

和Haskell代码:

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.Text.IO as T
import Text.XML.Cursor
import Text.XML

select3rd :: Name -> Axis
select3rd name =
    helper . (child >=> element name)
  where
      helper (_:_:x:_) = [x]
      helper _         = []

findNodes :: Cursor -> [Cursor]
findNodes =
    element "table" >=> attributeIs "class" "forumTable"
        &// element "tr" >=> select3rd "td"
        &/ element "a"

main :: IO ()
main = do
    doc <- Text.XML.readFile def "res/test.html"
    let cursor = fromDocument doc
    mapM_ T.putStrLn (cursor $.// findNodes &/ content)

需要一些时间才能了解所有这些运算符($.//&/等)是如何工作的,但在阅读了documentation几次并使用它们之后,事情变得更好。此外,我建议您仔细阅读解释CursorAxis的内容的部分。

上面的代码如下(教学上)。首先,我们使用提供的库函数将main cursor放入文档的根目录。然后我们将cursorfindNodescontent组合在一起。运算符$.//表示findNodes必须在当前游标(cursor)及其所有后代的上下文中运行。换句话说,它只是意味着findNodes将访问整个HTML树。试一试,使用$/代替$.//。您会注意到findNodes使用<table>找不到element "table"元素,因为<table>不在<html>的正下方(cursor 1}}目前指向)。然后,在findNodes中,我们使用element "table"运算符组合attributeIs "class" "forumTable">=>,因为右侧的函数(rhs)必须在Axis上执行由左手边(lhs)返回。

之后,我们使用&//运算符来过滤<tr>中的后代<table>。与以前一样,如果我们仅使用&/,则找不到<tr>,因为有一个封闭的<tbody>标记,&/只查看直接后代(或儿童)。请注意,当我们在lhs中有Cursor时,运算符以$开头,而当我们有Axis时,它们以&开头。最后,我们有select3rd轴,它允许在节点存在时对节点的第三个子节点进行命名选择。我们使用它来选择每个<td>中的第三个<tr>。希望,到现在为止,应该很容易理解它是如何运作的。

您的问题中的代码在使用child运算符的findNodes末尾呼叫>=>。只要您还将&/更改为content>=>,您就可以保留该调用(两种实现都是等效的。)