1

HXT を使用して、非常に適切に形成された HTMl テーブルのテーブルからレコードを抽出しようとしています。SO と HXT のドキュメントに関するいくつかの例を確認しました。たとえば、次のとおりです。

私の問題は次のとおりです。

既知の ID でテーブルを一意に識別し、そのテーブル内の各 tr に対してレコード オブジェクトを作成し、これをレコードのリストとして返します。

ここに私のHTMLがあります

<!DOCTYPE html>
<head>
  <title>FakeHTML</title>
</head>
<body>
  <table id="fakeout-dont-get-me">
    <thead><tr><td>Null</td></tr></thead>
    <tbody><tr><td>Junk!</td></tr></tbody>
  </table>
  <table id="Greatest-Table">
    <thead>
      <tr><td>Name</td><td>Favorite Rock</td></tr>
    </thead>
    <tbody>
      <tr id="rock1">
        <td>Fred</td>
        <td>Igneous</td>
      </tr>
      <tr id="rock2">
        <td>Bill</td>
        <td>Sedimentary</td>
      </tr>
    </tbody>
  </table>
</body>
</html>

これは、これを解析するための2つの異なるアプローチとともに、私が試しているコードです。まず、インポート...

{-# LANGUAGE Arrows, OverloadedStrings, DeriveDataTypeable, FlexibleContexts  #-}
import Text.XML.HXT.Core
import Text.HandsomeSoup
import Text.XML.HXT.XPath.XPathEval
import Data.Tree.NTree.TypeDefs
import Text.XML.HXT.XPath.Arrows

私が欲しいのは、たとえばからのRockrecsのリストです...

recs = [("rock1", "Name", "Fred", "Favorite Rock", "Igneous"),
        ("rock2", "Name", "Bill", "Favorite Rock", "Sedimentary")]

data Rockrec = Rockrec { rockID:: String,
                         rockName :: String,
                         rockFav :: String} deriving Show

rocks = [(\(a,_,b,_,c) -> Rockrec a b c ) r | r <- recs]
-- [Rockrec {rockID = "rock1", rockName = "Fred", rockFav = "Igneous"},
--  Rockrec {rockID = "rock2", rockName = "Bill", rockFav = "Sedimentary"}]

[XMLTree] の束を返した後、runLA でバインドを使用する最初の方法を次に示します。つまり、適切なテーブルを取得するためだけに最初の解析を行い、その最初のグラブの後でツリー行を処理します。

試行 1

getTab = do
  dt <- Prelude.readFile "fake.html"
  let html = parseHtml dt
  tab <- runX $ html //> hasAttrValue "id" (== "Greatest-Table")
  return tab
  -- hmm, now this gets tricky...

-- table <- getTab

node tag = multi (hasName tag)

-- a la https://stackoverflow.com/questions/3901492/running-haskell-hxt-outside-of-io?rq=1
getIt  :: ArrowXml cat => cat (Data.Tree.NTree.TypeDefs.NTree XNode) (String, String)
getIt = (node "tr" >>>
         (getAttrValue "id" &&& (node "td" //> getText)))

これはちょっとうまくいきます。少しマッサージする必要がありますが、実行できます...

-- table >>= runLA getIt
-- [("","Name"),("","Favorite Rock"),("rock1","Fred"),("rock1","Igneous"),("rock2","Bill"),("rock2","Sedimentary")]

これは、 https://wiki.haskell.org/HXT/Practical/Simple1に触発された 2 番目のアプローチ です。ここで、{-# LANGUAGE Arrows -} の何かに依存していると思います (偶然にも上記の rec のリスト内包表記が壊れています)、より読みやすい do ブロックでこれを行うために proc 関数を使用します。とはいえ、これの最小バージョンをコンパイルすることさえできません。

試行 2

 getR :: ArrowXml cat => cat XmlTree Rockrec
 getR = (hasAttrValue "id" (== "Greatest-Table")) >>>
   proc x -> do
     rockId <- getText -< x
     rockName <- getText -< x
     rockFav <- getText -< x
     returnA -< Rockrec rockId rockName rockFav

編集

アレックからの以下のコメントに応じて、タイプの問題

λ> getR [table]

<interactive>:56:1-12: error:
    • Couldn't match type ‘NTree XNode’ with ‘[[XmlTree]]’
      Expected type: [[XmlTree]] -> Rockrec
        Actual type: XmlTree -> Rockrec
    • The function ‘getR’ is applied to one argument,
      its type is ‘cat0 XmlTree Rockrec’,
      it is specialized to ‘XmlTree -> Rockrec’
      In the expression: getR [table]
      In an equation for ‘it’: it = getR [table]
λ> getR table

<interactive>:57:1-10: error:
    • Couldn't match type ‘NTree XNode’ with ‘[XmlTree]’
      Expected type: [XmlTree] -> Rockrec
        Actual type: XmlTree -> Rockrec
    • The function ‘getR’ is applied to one argument,
      its type is ‘cat0 XmlTree Rockrec’,
      it is specialized to ‘XmlTree -> Rockrec’
      In the expression: getR table
      In an equation for ‘it’: it = getR table

編集終了

要素を選択していなくても、上記を実行できません。また、最初の td を rockName に、2 番目の td を rockFav に配置する方法、これらにイテレータを含める方法についても少し困惑しています (2 つだけではなく、多くの td フィールドがあると仮定します)。

これを行う方法に関するその他の一般的なヒントは、より簡単に高く評価されます。

4

1 に答える 1

1

HXT/Practical/Google1から、解決策をまとめることができると思います。

{-# LANGUAGE Arrows #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Hanzo where
import Text.HandsomeSoup
import Text.XML.HXT.Cor

atTag tag =
  deep (isElem >>> hasName tag)
text =
  deep isText >>> getText

data Rock = Rock String String String deriving Show    
rocks =
  atTag "tbody" //> atTag "tr"
  >>> proc x -> do
        rowID <- x >- getAttrValue "id"
        name <- x >- atTag "td" >. (!! 0) >>> text
        kind <- x >- atTag "td" >. (!! 1) >>> text
        returnA -< Rock rowID name kind

main = do
  dt <- readFile "html.html"
  result <- runX $ parseHtml dt
                   //> hasAttrValue "id" (== "Greatest-Table")
                   >>> rocks
  print result

重要なポイントは次のとおりです。

  • 矢印は要素のストリームでは機能しますが、個々の要素では機能しません。これがArrowList制約です。したがって、 を 3 回呼び出すgetTextと、驚くべき動作が発生しgetTextます。<table>proc x -> do {...}

  • <tr>代わりにできることは、必要なストリーム、つまり 内の のストリームに焦点を当てることです<tbody>。テーブルの行ごとに、ID 属性値と最初の 2 つ<td>の のテキストを取得します。

  • これは最も洗練された解決策とは思えませんが、ストリームにインデックスを付ける方法の 1 つは、(>.) :: ArrowList cat => cat a b -> ([b] -> c) -> cat a cコンビネータを使用してストリームをフィルタリングすることです。

  • 最後にもう 1 つ、実際の wiki の例で気づいたトリックがあります。必要なノードだけを使用deepisElem/isTextて集中することができます。XML ツリーがうるさい!

于 2016-12-19T08:18:05.580 に答える