Suppose I have this MathML document
<?xml version="1.0" encoding="UTF-8”?>
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mi> f </mi>
<mo> ⁡ </mo>
<mrow>
<mo> ( </mo>
<mi> x </mi>
<mo> ) </mo>
</mrow>
</math>
Suppose I want to “lift” the children of mi
and mrow
, I should get (let’s ignore the whitespace change here)
<?xml version="1.0" encoding="UTF-8"?>
<math xmlns="http://www.w3.org/1998/Math/MathML">
f
<mo> ⁡ </mo>
<mo> ( </mo>
x
<mo> ) </mo>
</math>
How should I write that with HXT?
I’m a Haskell newbie... so all I have right now is
-- Dealing with command line arguments and stuff…
processRootElement :: IOSArrow XmlTree XmlTree
processRootElement
= processTopDown -- What goes here?
XmlTree = NTree XNode
meaning that every XmlTree
has the construction
NTree XNode [XMLTree]
Where the first argument is the current node, and the second argument is the list of children.
processTopDown
will take the tree transformation you provide and produce a tree transformation which applies it recursively.
First, let's define the tree transformation you want on a single node:
The transformation doesn't "lift" the children of current node, because that wouldn't be possible on the root.
A good way to do this would be to use processChildren
, which is an arrow which lets us specify new children for the current node based on the old children. To do this, we'll need to use conditional arrows
We can then split the design into two parts, a predicate for matching the tags we want, and the transformation we want to perform
Reminding ourselves of what forms an XNode
can take, the one we're interested in is
XTag QName XmlTrees
We want to match on nodes of this form, for our given tag names. For this, we write a helper function
filterOnQName :: QName -> XNode -> Bool
filterOnQName qname (XTag xqname _)
| qname == xqname = True
| otherwise = False
filterOnQName _ _ = False
For ease of use, we want to write the tags as strings, so we'll use mkName
to convert them into QName
s. Then our more useful filter function is
filterTags :: [String] -> XmlTree -> Bool
filterTags tagNames (NTree xnode _) = any (\qname -> filterOnQName qname xnode) (map mkName tagNames)
But this isn't an arrow, which we need it to be for reasons we'll see later. We can simply turn it into one with isA
childFilter tags = isA (filterTags tags)
We're going two need two arrows for the transformation body - one for when the filter matches, and one for when it doesn't.
For when it doesn't, the transformation is easy - we want to keep the current node.
filterFailed = this
Here, this
is the identity arrow - it does nothing.
When the filter matches, we want to get the children - first, let's write a helper
getChildren :: XmlTree -> [XmlTree]
getChildren (NTree _ children) = children
Handily, because we're working with list arrows, we can turn this straight into an arrow using arrL
liftChildren = arrL getChildren
We can now turn this into a single arrow, using ifA
, the arrow version of if
liftMatchedChildren tags = ifA (childFilter tags) liftChildren filterFailed
And finally, we can describe the transformation we wanted
processRootElement
= processTopDown (processChildren (liftMatchedChildren ["mi", "mrow"]))