Записи из <tr>в Html-таблице с использованием стрелок и HXT в Haskell
Нужно извлечь записи из таблицы в очень хорошо сформированную таблицу HTMl, используя HXT. Я рассмотрел несколько примеров SO и документации HXT, таких как:
- Извлечение значений из поддерева
- http://adit.io/posts/2012-04-14-working_with_HTML_in_haskell.html
- https://www.schoolofhaskell.com/school/advanced-haskell/xml-parsing-with-validation
- Запуск Haskell HXT вне IO?
- извлечь несколько таблиц HTML с HXT
- Разбор HTML в Haskell
- http://neilbartlett.name/blog/2007/08/01/haskell-explaining-arrows-through-xml-transformationa/
- https://wiki.haskell.org/HXT/Practical/Simple2
- https://wiki.haskell.org/HXT/Practical/Simple1
- Сгруппировать строки таблицы HTML с HXT в Haskell
- Разбор нескольких дочерних узлов в Haskell с помощью HXT
Моя проблема:
Я хочу однозначно идентифицировать таблицу по известному идентификатору, а затем для каждого 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>
Вот код, который я пытаюсь, наряду с двумя различными подходами к анализу этого. Во-первых, импорт...
{-# 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"}]
Вот мой первый способ, который использует связывание на runLA после того, как я верну связку [XMLTree]. То есть я делаю первый анализ только для того, чтобы получить правильную таблицу, затем обрабатываю строки дерева после первого захвата.
Попытка 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://stackru.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. Здесь, я думаю, я полагаюсь на что-то в {-# LANGUAGE Arrows -} (что по совпадению нарушает мое понимание списка для rec выше), чтобы использовать функцию proc, чтобы сделать это в более читаемом блоке do. Тем не менее, я даже не могу получить минимальную версию этого для компиляции:
Попытка 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
КОНЕЦ РЕДАКТИРОВАНИЯ
Даже если я не выбираю элементы, я не могу запустить вышеизложенное. Я также немного озадачен тем, как я должен сделать что-то вроде того, как поместить первый тд в rockName и второй тд в rockFav, как включить в них итератор (предположим, у меня много полей тд, а не просто 2.)
Любые дальнейшие общие советы о том, как это сделать, более безболезненно приветствуются.
1 ответ
От https://wiki.haskell.org/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
ограничение. Таким образом, призываяgetText
три раза даст удивительное поведение, потому чтоgetText
представляет все возможные значения текста, которые вы можете получить в процессе потоковой передачи<table>
элементы через вашproc x -> do {...}
,Вместо этого мы можем сосредоточиться на желаемом потоке: потоке
<tr>
с внутри<tbody>
, Для каждой строки таблицы мы берем значение атрибута ID и текст первых двух<td>
s.Это не кажется самым элегантным решением, но один из способов, которым мы можем индексировать поток, - отфильтровать его с помощью
(>.) :: ArrowList cat => cat a b -> ([b] -> c) -> cat a c
комбинатор.Последний трюк, который я заметил в практических вики-примерах: мы можем использовать
deep
а такжеisElem/isText
сосредоточиться только на узлах, которые мы хотим. XML-деревья шумят!