HXT:在Haskell中使用HXT按位置选择节点?

时间:2013-07-22 22:18:03

标签: haskell arrows hxt

我正在尝试使用Haskell解析一些XML文件。对于这项工作,我正在使用HXT来获取有关实际应用程序中箭头的一些知识。所以我对箭头话题很新。

在XPath(和HaXml)中,可以按位置选择节点,例如:/root/a[2]/b

即使在一次又一次阅读文档之后,我也无法弄清楚如何使用HXT做类似的事情。

以下是我正在使用的一些示例代码:

module Main where

import Text.XML.HXT.Core

testXml :: String
testXml = unlines
    [ "<?xml version=\"1.0\"?>"
    , "<root>"
    , "    <a>"
    , "        <b>first element</b>"
    , "        <b>second element</b>"
    , "    </a>"
    , "    <a>"
    , "        <b>third element</b>"
    , "    </a>"
    , "    <a>"
    , "        <b>fourth element</b>"
    , "        <b>enough...</b>"
    , "    </a>"
    , "</root>"
    ]

selector :: ArrowXml a => a XmlTree String
selector = getChildren /> isElem >>> hasName "a" -- how to select second <a>?
                       /> isElem >>> hasName "b"
                       /> getText

main :: IO ()
main = do
    let doc = readString [] testXml
    nodes <- runX $ doc >>> selector
    mapM_ putStrLn nodes

所需的输出是:

third element

提前致谢!

2 个答案:

答案 0 :(得分:4)

我相信选择“/ root / a [2] / b”的解决方案(第二个“a”标签内的所有“b”标签):

selector :: ArrowXml a => Int -> a XmlTree String
selector nth =
    (getChildren /> isElem >>> hasName "a")   -- the parentheses required!
    >. (!! nth) 
    /> isElem >>> hasName "b" /> getText

(结果为["third element"])。

说明:正如我所见,class (..., ArrowList a, ...) => ArrowXml aArrowXml aArrowList的子类。浏览ArrowList界面:

(>>.) :: a b c -> ([c] -> [d]) -> a b d
(>.) :: a b c -> ([c] -> d) -> a b d

因此>>.可以使用提升的[c] -> [d]选择列表的子集,>.可以使用类型为[c] -> d的提升函数从列表中选择单个项目。因此,在选择子项并标记“a”后,让我们使用(!! nth) :: [a] -> a

有一点需要注意:

infix 1 >>>
infix 5 />
infix 8 >.

(所以我很难弄清楚为什么没有括号的>.不能按预期工作)。因此,getChildren /> isElem >>> hasName "a"必须用括号括起来。

答案 1 :(得分:0)

这只是EarlGray对答案的延伸。请参阅>>.>.的说明!在提出问题之后,我意识到我需要以特殊和确定的方式穿过树。所以这是我用于解决具体问题的解决方案。对于其他人试图完成同样的事情,我想分享示例代码。

假设我们要提取第一个<a>和第二个<b>的文字。并非所有<a>元素都至少有两个<b>,因此EarlGray的代码会挽救,因为您无法使用(!!)函数(空列表!)

查看Control.Arrow.ArrowList中的single函数,该函数仅使用列表箭头的第一个结果:

single :: ArrowList a => a b c -> a b c
single f = f >>. take 1

我们想要提取第n个元素:

junction :: ArrowList a => a b c -> Int -> a b c
junction a nth = a >>. (take 1 . drop (nth - 1))

现在我们可以使用这个新箭头来构建选择器。我们将使用junction过滤掉我们要过滤的内容的括号,因为junction会修改现有箭头。

selector :: ArrowXml a => a XmlTree String
selector = getChildren -- There is only one root element.
         -- For each selected element: Get a list of all children and filter them out.
         -- The junction function now selects at most one element.
         >>> (getChildren >>> isElem >>> hasName "a") `junction` 1 -- selects first <a>
         -- The same thing to select the second <b> for all the <a>s
         -- (But we had selected only one <a> in this case!
         -- Imagine commenting out the `junction` 1 above.)
         >>> (getChildren >>> isElem >>> hasName "b") `junction` 2 -- selects second <b>
         -- Now get the text of the element.
         >>> getChildren >>> getText

提取值并返回Maybe值:

main :: IO ()
main = do
    let doc = readString [] testXml
    text <- listToMaybe <$> (runX $ doc >>> selector)
    print text

这会输出Just "second element"示例XML文件。