2014-09-25 68 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