In short, here is what I want to accomplish:
"foo.xml"
:
<?xml version="1.0"?>
<foo>
<bar>
<baz>
<a>foo</a>
<a>bar</a>
<a>baz</a>
</baz>
</bar>
</foo>
expected result (contents of "bar.xml"
):
<?xml version="1.0"?>
<foo>
<bar>
<baz>
<a>foo!</a>
<a>bar!</a>
<a>baz!</a>
</baz>
</bar>
</foo>
...my attempt to approach the problem:
module Main (main) where
import Control.Monad
import Control.Arrow.ArrowTree
import Text.XML.HXT.Core
main :: IO ()
main = void . runX $ readDocument [] "foo.xml" >>>
applic >>> writeDocument [withIndent yes] "bar.xml"
applic :: IOSArrow XmlTree XmlTree
applic = getChildren >>> hasName "foo"
-- ^^ because of extra root node (?)
/> hasName "bar" /> hasName "baz" /> hasName "a" >>> changeText excl
excl :: String -> String
excl = (++ "!")
Question: How to directly edit just selected elements without changing/removing their root elements? Also note that this program doesn't create "bar.xml"
file, so something is definitely wrong. Tracing shows that after applying applic
arrow, the document consists of three a
elements ("foo"
, "bar"
, and "baz"
; without exclamation points).
I don't pretend to be good at HXT, I haven't used it much, but I've gotten what you want to do to work through some experimentation. If someone has more experience with HXT feel free to offer a better solution.
I found through skimming over the HXT wiki the process*
functions, like processTopDown
and processChildren
, along with several others. These seem to be what actually allows change to happen. Now, I'm assuming that your actual use case is more complex, you may only want to select elements at a certain level. The pattern I stumbled upon was to use processChildren
along with the HXT version of when
, not the Control.Monad
one since they are not the same. Basically, my first implementation was
applic
= processChildren
$ flip when (isElem >>> hasName "foo")
$ processChildren
$ flip when (isElem >>> hasName "bar")
$ processChildren
$ flip when (isElem >>> hasName "baz")
$ processChildren
$ flip when (isElem >>> hasName "a")
$ processChildren
$ flip when isText
$ changeText excl
And this is really quite ugly to me, there's just way too much repetition. So I abstracted this out to something that is much more readable:
-- Fixity is important here, must be right-associative.
infixr 5 />/
(/>/) :: ArrowXml a => String -> a XmlTree XmlTree -> a XmlTree XmlTree
name />/ action
= processChildren
$ action `when` (isElem >>> hasName name)
applic = "foo" />/ "bar" />/ "baz" />/ "a" />/
processChildren (
changeText excl `when` isText
)
All these calls to processChildren
might be redundant, particularly if you're just drilling down into the structure, but it definitely works and it won't modify other a
elements in different parts of the file.