HaskellでのXMLパース処理 - XPath使用

今回は、id:fits:20100525 で実施したパース処理を Haskell で書いてみました。
環境は以下の通りで、XML の処理のために HaXml というライブラリをインストールしました。

  • GHC 6.12.1
  • HaXml 1.20.2

Haskell での XPath

HaXml では、xmlParse で XML をパース、xtract で XPath を処理し、tagTextContent で要素内のテキストを取得できます。
tagTextContent の代わりに Text.XML.HaXml.Pretty の content を使えば要素全体を取得できます。

なお、xtract の型宣言が xtract :: (String -> String) -> String -> Content i -> [Content i] となっているため、(String -> String) の箇所に、引数の値をそのまま返す id 関数をあてがっています。

xbrl_parse.hs
import System
import Text.XML.HaXml
import Text.XML.HaXml.Posn
import Text.XML.HaXml.Util
import Text.XML.HaXml.Xtract.Parse

main = do
    args <- getArgs
    contents <- readFile $ head args

    let
        -- XML のパース
        -- データコンストラクタを使ったパターンマッチで Element 取得
        Document _ _ root _ = xmlParse "" contents
        -- Content 取得
        cont = CElem root noPos

    mapM_ (print . tagTextContent) $ xtract id "//jpfr-t-cte:OperatingIncome[@contextRef='CurrentYearConsolidatedDuration']" cont

-- 以下でも可
--  let Elem n _ cont = root
--  mapM_ (print . tagTextContent) $ concatMap (xtract id "//jpfr-t-cte:OperatingIncome[@contextRef='CurrentYearConsolidatedDuration']") cont


型が分かり易くなるように、関数を使って書き直すと以下のようになります。

xbrl_parse2.hs
import System
import Text.XML.HaXml
import Text.XML.HaXml.Posn
import Text.XML.HaXml.Util
import Text.XML.HaXml.Xtract.Parse

-- XPath で検索
findNodeList :: String -> Content Posn -> [Content Posn]
findNodeList pattern ct = xtract id pattern ct

-- Element を Content へ
toContent :: Element Posn -> Content Posn
toContent el = CElem el noPos

-- XMLのパース
parseXmlString :: String -> Element Posn
parseXmlString ct =
    let Document _ _ root _ = xmlParse "" ct
    in root

main = do
    args <- getArgs
    contents <- readFile $ head args

    let nodeList = findNodeList "//jpfr-t-cte:OperatingIncome[@contextRef='CurrentYearConsolidatedDuration']" (toContent $ parseXmlString contents)

    mapM_ (print . tagTextContent) nodeList

XPath を使わない方法

XPath を使うよりは冗長になってしまいますが、deep, tag, attrval を使う事で同等の処理が可能です。

xbrl_parse3.hs
import System
import Text.XML.HaXml
import Text.XML.HaXml.Posn
import Text.XML.HaXml.Util

main = do
    args <- getArgs
    contents <- readFile $ head args

    let
        Document _ _ root _ = xmlParse "" contents
        cont = CElem root noPos

    mapM_ (print . tagTextContent) $ (deep $ tag "jpfr-t-cte:OperatingIncome" `with` attrval ("contextRef", AttValue [Left "CurrentYearConsolidatedDuration"])) cont