是的,这将是构建此代码的好方法。使用你的数据定义,我很容易弄清楚如何将你的示例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