ここに私にとってかなり良いと思われる解決策があります
{-# LANGUAGE Arrows #-}
import Data.Maybe
import Text.Read
import Text.XML.HXT.Core
import Control.Applicative
data Gpx = Gpx [Trk] deriving (Show)
data Trk = Trk [TrkSeg] deriving (Show)
data TrkSeg = TrkSeg [TrkPt] deriving (Show)
data TrkPt = TrkPt Double Double deriving (Show)
最も難しいのはおそらく、parseTrkPt
正しく行うためにString
s から への解析を処理する必要があるためDouble
です。これは失敗する可能性があります。代わりにa を返すように決定し、Maybe TrkPt
それをさらに下に処理します。
elemsNamed :: ArrowXml cat => String -> cat XmlTree XmlTree
elemsNamed name = isElem >>> hasName name
parseTrkPt :: ArrowXml cat => cat XmlTree (Maybe TrkPt)
parseTrkPt = elemsNamed "trkpt" >>>
proc trkpt -> do
lat <- getAttrValue "lat" -< trkpt
lon <- getAttrValue "lon" -< trkpt
returnA -< TrkPt <$> readMaybe lat <*> readMaybe lon
proc
ここでも構文を使用しました。にはTrkPt <$> readMaybe lat <*> readMaybe lon
型がMaybe TrkPt
あり、 のいずれかが を返したNothing
場合にreadMaybe
戻りますNothing
。これで、すべての成功した結果を集計できます。
parseTrkSeg :: (ArrowXml cat, ArrowList cat) => cat XmlTree TrkSeg
parseTrkSeg =
elemsNamed "trkseg" >>>
(getChildren >>> parseTrkPt >>. catMaybes) >. TrkSeg
ここでは括弧が重要です。その部分を理解するのにしばらく時間がかかりました。かっこを配置する場所に応じて、[TrkSeg [TrkPt a b], TrkSeg [TrkPt c d]]
代わりになど、異なる結果が得られます[TrkSeg [TrkPt a b, TrkPt c d]]
。次のパーサーは、どちらも同様のパターンに従って単純です。
parseTrk :: ArrowXml cat => cat XmlTree Trk
parseTrk =
elemsNamed "trk" >>>
(getChildren >>> parseTrkSeg) >. Trk
parseGpx :: ArrowXml cat => cat XmlTree Gpx
parseGpx =
elemsNamed "gpx" >>>
(getChildren >>> parseTrk) >. Gpx
その後、非常に簡単に実行できますが、ルート要素をドリルダウンする必要があります。
main :: IO ()
main = do
gpxs <- runX $ readDocument [withRemoveWS yes] "ana.gpx"
>>> getChildren
>>> parseGpx
-- Pretty print the document
forM_ gpxs $ \(Gpx trks) -> do
putStrLn "GPX:"
forM_ trks $ \(Trk segs) -> do
putStrLn "\tTRK:"
forM_ segs $ \(TrkSeg pts) -> do
putStrLn "\t\tSEG:"
forM_ pts $ \pt -> do
putStr "\t\t\t"
print pt
秘訣は、特にtype を持つArrowList
typeclass でメソッドを使用することです。から要素を集約し、それを新しい型に変換する関数に渡し、その新しい型で新しいを出力します。>.
a b c -> ([c] -> d) -> a b d
ArrowList
ArrowList
d
必要に応じて、最後の 3 つのパーサーについてこれを少し抽象化することもできます。
nestedListParser :: ArrowXml cat => String -> cat XmlTree a -> ([a] -> b) -> cat XmlTree b
nestedListParser name subparser constructor
= elemsNamed name
>>> (getChildren >>> subparser)
>. constructor
parseTrkSeg :: (ArrowXml cat, ArrowList cat) => cat XmlTree TrkSeg
parseTrkSeg = nestedListParser "trkseg" (parseTrkPt >>. catMaybes) TrkSeg
parseTrk :: ArrowXml cat => cat XmlTree Trk
parseTrk = nestedListParser "trk" parseTrkSeg Trk
parseGpx :: ArrowXml cat => cat XmlTree Gpx
parseGpx = nestedListParser "gpx" parseTrk Gpx
これは、GPX ファイルの残りの文法を完成させたい場合に便利です。