I read this question while trying to do something similar. The answer given there does not solve my problem.
I want to use a visit statement to determine the 'mass' of each subtree, so for each node I want to sum the masses of all descendants. For example, a visiting step which encounters this node expression with a list in it:
\anode([\bnode()[@mass=1], \bnode()[@mass=2]], \cnode()[@mass=5])
should yield this:
\anode([\bnode()[@mass=1], \bnode()[@mass=2]], \cnode()[@mass=5])[@mass=8]
So I do not just want to filter out the non-nodes, but actually traverse them. Including a case for lists in my visit statement does not work (at least not obviously) because lists cannot have annotations.
Is there a natural way to propagate the complete annotation information up a tree?
This first solution is precise and useful if the number of nodes with container children is not so large:
(You'd probably also abstract the reducer expression in a local function)
The second solution would be to abstract from the node type, in case you have many of these:
The third solution would work best in case the annotations are nested even deeper, as in lists of lists etc, and you have many different types of nodes:
I prefer the very first solution because it is most precise.
Note that we are replacing the annotations feature by "keyword parameters" in the near future; with almost the same semantics, different syntax, like
\cnode(mass=2)
for\code()[@mass=2]
. With the new keyword parameters you would also have a lazily computed default value for the mass field, like so: