f &ApplyF" /> f &ApplyF" /> f &ApplyF"/>

HXT: how to “lift” children of certain elements?

117 Views Asked by At

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> &ApplyFunction; </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> &ApplyFunction; </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?
1

There are 1 best solutions below

3
Isaac van Bakel On BEST ANSWER

Quick type rundown

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.

The creative process

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:

  1. Go through the children of the current node
  2. If any match the tags we specify, then
    1. Take all the children of the tag, and
    2. Make them children of the current node instead
    3. Then remove the tag

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

The predicate

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 QNames. 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)

The transformation

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

Combining them

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"]))