🏹
XMLファイルからコンテンツを集めて1つのXMLにする(HXTで)
いつも忘れる&ハマるのでメモ。
たとえば、複数のHTMLファイルの「body
要素の中身だけ」を連接して1つのHTMLファイルにしたいとする。つまり、以下のような2つのHTMLファイルから…、
<html>
<head>...</head>
<body>
<h1>Title 1</h1>
<p>foo & baz</p>
</body>
</html>
<html>
<head>...</head>
<body>
<h1>Title 2</h1>
<p>hoge & fuga</p>
</body>
</html>
次のようなHTMLファイルを作りたいとする。
<html>
<head>...</head>
<body>
<h1>Title 1</h1>
<p>foo & baz</p>
<h1>Title 2</h1>
<p>hoge & fuga</p>
</body>
</html>
「body
要素の中身」を取り出すのはHXTなら簡単で、hasName "body" >>> getChildren
とすればいいんだけど、readDocument
では1つのファイルしか開けないから、IOモナドの中でmapM
(もしくは forM
)して「body
要素の中身」の XTree
を取り出し、それらを xshow
した String
を連接すればいい。
module Main where
import Text.XML.HXT.Core hiding (xshow)
import Text.XML.HXT.DOM.ShowXml (xshow)
import System.Environment
import Control.Monad
main = do
files <- getArgs
contents <- forM files $ \s -> do
body <- runX $
readDocument [] s
>>>
(multi $ hasName "body" >>> getChildren)
return $ body
putStrLn $ "<body>" <> concatMap xshow contents <> "</body>"
しかし、実は xshow
には罠があって、文字参照を勝手に解釈済みの文字に変換してしまう。つまり上記の例だと &
がすべて &
になってしまう。まじかよ。
$ runghc concat.hs temp1.html temp2.html
<body>
<h1>Title 1</h1>
<p>foo & baz</p>
<h1>Title 2</h1>
<p>hoge & fuga</p>
</body>
回避策としては xshowEscapeXml
という関数を使う。ところが、これはArrow用なので、runX
の中でしか使えない。
module Main where
import System.Environment
import Text.XML.HXT.Core
import Control.Monad
main = do
files <- getArgs
contents <- forM files $ \s -> do
body <- runX $
xshowEscapeXml $
readDocument [] s
>>>
(multi $ hasName "body" >>> getChildren)
return $ body
putStrLn $ "<body>" <> (concat . concat) contents <> "</body>"
これで目的の挙動が得られる(<html>
タグとか<head>
の中身とかはなんとでもなるものとする)。
$ runghc concat.hs temp1.html temp2.html
<body>
<h1>Title 1</h1>
<p>foo & baz</p>
<h1>Title 2</h1>
<p>hoge & fuga</p>
</body>
Discussion