🏹

XMLの親の属性を子に移動する(HXTで)

1 min read

XMLで、親についている属性を、子の要素に付け替えたいとする。つまり、たとえば以下のようなHTMLがあったとき…

<body>
<table id="1">
  <caption>title</caption>
</table>
</body>

以下のように修正したいとする。 id="1"<table> から <chapter> に移動しているのがわかると思う。

<body>
<table>
  <caption id="1">title</caption>
</table>
</body>

ふつうはどうやるのが一般的なんだろう。やはりXSLTかな。

ぼくはいまはHaskellのHXTというライブラリでこの手の処理を書いているのだけれど、いまのように「親の情報を子で使いたい」場合、ちょっと手が止まってしまう。HXTについては、毎回Arrowのノリを忘れるので、このへんのサンプル や、自分の過去記事を眺めて思い出す必要があるのだけど、「親の情報を子で使いたい」場合のこれという例が載っていない。

で、こういうときは $< を使う(のだと思ってるけどもっといい方法があるのかもしれない。親の情報を引数か何かに保持したうえで子の処理をすればいいので、{-# LANGUAGE Arrows #-} として proc を使うほうがわかりやすいのかもしれないが、いまいち processTopDown の中に書きにくいのだよな)。

module Main where

import Text.XML.HXT.Core
import Text.XML.HXT.Arrow.XmlArrow
import Control.Arrow

main :: IO ()
main = do
  runX (readDocument [] "test.html"
        >>>
        (processTopDown
          (ifA (hasName "table")
           ((\idv ->
              (processTopDown 
	        (ifA (hasName "caption")
		     (addAttr "id" idv)
		     (this))))
            $< (this >>> getAttrValue "id")
	    >>> removeAttr "id")
           (this)))
        >>>
        writeDocument [] "result.html"
        )
  return ()

$< の後ろには、親の情報を取り出すようなArrowを書く。この時点では、まだ親を見ているので、このときの this が親なのがポイント。

$< の前には、引数を1つとってArrowを返すような関数を書く。子をめぐって何かしたいので、(processTopDown ...)(processBottomUp ...) あたりを置き、引数を使った処理を書くことになる。

とりあえずこのパターンで、親の情報を子で使う処理はだいたい書ける。引数がもっと欲しければ$< の後に &&& で複数のArrowを並べればいい。