是的,這將是構建此代碼的好方法。使用你的數據定義,我很容易弄清楚如何將你的示例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
雖然通過鏡頭的魔力,你很可能使這個短。
我喜歡mapMaybe和parseXYZ一起玩的方式。它導致了一個非常清晰和簡潔的實現。你對我的問題的回答也可以這樣說。 :) – fuji 2014-09-25 19:15:12
@ j.dog很高興聽到它。你當然可以減少一些重複在這一點上更清潔,我想象的更像是[this](https://gist.github.com/bheklilr/3f6dd0928effd4fd37bf) – bheklilr 2014-09-25 19:51:53