2014-09-25 45 views
2

我有一个很大的.xml文件,我使用Text.XML.Light在Haskell中加载和解析。文件中的元素表示不同的对象。它看起来像这样:检索haskell中xml的异质元素

<matchhistory> 
    <game starttime="2/10/13 18:00" endtime="2/11/13 18:40"> 
    <participant> 
     <name>John Doe</name> 
     <color>green</color> 
    </participant> 
    <participant> 
     <name>Jane Doe</name> 
     <color>blue</color> 
    </participant> 
    <winner color="blue"> 
    </game> 
    <game starttime="2/11/13 17:00" endtime="2/11/13 17:30"> 
    <participant> 
     <name>Jane Doe</name> 
     <color>green</color> 
    </participant> 
    <participant> 
     <name>John Doe</name> 
     <color>blue</color> 
    </participant> 
    <winner color="green"> 
    </game> 
    ... 
</matchhistory> 

我想循环遍历节点并检索对象。将数据存储在Haskell中进行进一步处理的好方法是什么?举个例子,我对他们在比赛中的平均时间感兴趣。

以类似的嵌套方式创建数据类型是否有意义?

data Participant = Participant {name :: String, 
           color :: String} 

data Game = Game {starttime :: UTCTime, 
        endtime :: UTCTime, 
        participants :: [Participant], 
        winner :: String} 

data MatchHistory = MatchHistory {games :: [Game]} 

我该如何执行这个转换,通过整个文件(26K行)分别提取每个对象类型看起来不是很优雅?有没有一种很好的方法(可能与调用不同构造函数的模式匹配)在一次扫描中这样做?

回答

2

是的,这将是构建此代码的好方法。使用你的数据定义,我很容易弄清楚如何将你的示例XML文档转换为数据结构。这里的技巧是构建增量式解析器。

首先,我们需要一些进口和数据定义的重述:

import Data.Time (UTCTime) 
import Data.Time.Format (parseTime) 
import System.Locale (defaultTimeLocale) 
import Data.Maybe (mapMaybe) 
import Text.XML.Light 
import Control.Applicative ((<$>), (<*>)) 

data Participant = Participant 
    { name :: String 
    , color :: String 
    } deriving (Eq, Show) 

data Game = Game 
    { starttime  :: UTCTime 
    , endtime  :: UTCTime 
    , participants :: [Participant] 
    , winner  :: String 
    } deriving (Eq, Show) 

data MatchHistory = MatchHistory 
    { games :: [Game] 
    } deriving (Eq, Show) 

我们可以使用parseXML功能Text.XML.Light将字符串转换为[Content]。接下来,我们可以使用onlyElems提取所有顶级元素。为了解析,我们希望每个解析能够优雅地失败,所以现在我们只使用Maybe monad。我们可以为每个数据类型的存根:

parseParticipant :: Element -> Maybe Participant 
parseParticipant = undefined 

parseGame :: Element -> Maybe Game 
parseGame = undefined 

parseMatchHistory :: Element -> Maybe MatchHistory 
parseMatchHistory = undefined 

这让我们写一个文档解析器:

parseDocument :: String -> [MatchHistory] 
parseDocument = mapMaybe parseMatchHistory . onlyElems . parseXML 

现在,对于每个解析的实现:

parseParticipant pElem = Participant 
    <$> (strContent <$> findChild (blank_name { qName = "name" }) pElem) 
    <*> (strContent <$> findChild (blank_name { qName = "color" }) pElem) 

这一个很容易,我们有两个文本字段,我们所要做的就是查找每个字段然后提取文本。我在这里选择了应用风格,因为我认为它更容易,但我们也会使用monadic风格。

parseGame gElem = do 
    starttimeStr <- findAttrBy (("starttime" ==) . qName) gElem 
    endtimeStr <- findAttrBy (("endtime" ==) . qName) gElem 
    winnerElem <- findChild (blank_name { qName = "winner" }) gElem 
    let pElems = filterChildrenName (("participant" ==) . qName) gElem 
    ... 

现在,我们已经提取的几乎所有从我们所需要的游戏元素的信息,我们只需要在时间字符串解析成UTCTime,分析每个参与者,然后返回一个Game值。对于时间分析,我将介绍一个额外的功能,以方便:

parseTimeField :: String -> Maybe UTCTime 
parseTimeField = parseTime defaultTimeLocale "%-m/%-d/%-y %R" 

parseGame gElem = do 
    starttimeStr <- findAttrBy (("starttime" ==) . qName) gElem 
    endtimeStr <- findAttrBy (("endtime" ==) . qName) gElem 
    winnerElem <- findChild (blank_name { qName = "winner" }) gElem 
    let pElems = filterChildrenName (("participant" ==) . qName) gElem 
    Game <$> parseTimeField starttimeStr 
     <*> parseTimeField endtimeStr 
     <*> pure (mapMaybe parseParticipant pElems) 
     <*> findAttrBy (("color" ==) . qName) winnerElem 

最后,我们需要实现parseMatchHistory。我会把这个作为练习留给你,但它应该很容易。

一旦你有一个分析的文档,你可以做这样的事情

averageTimePlayed :: MatchHistory -> DiffTime 
averageTimePlayed = average . map calcDiff . games 
    where 
     average xs = sum xs/fromIntegral (length xs) 
     calcDiff g = endtime g `diffUTCTime` starttime g 

虽然通过镜头的魔力,你很可能使这个短。

+0

我喜欢mapMaybe和parseXYZ一起玩的方式。它导致了一个非常清晰和简洁的实现。你对我的问题的回答也可以这样说。 :) – fuji 2014-09-25 19:15:12

+0

@ j.dog很高兴听到它。你当然可以减少一些重复在这一点上更清洁,我想象的更像是[this](https://gist.github.com/bheklilr/3f6dd0928effd4fd37bf) – bheklilr 2014-09-25 19:51:53