Bit of a bizarre one, this, and I'm tempted to assume it may be a bug. Background - I have a whole load of XML that looks like this:
<foo id="1">
<bar id="0">
<baz id="0" blah="blah" etc="etc">
<buz id="0" />
</baz>
<buz id="0" blah="blah" etc="etc">
...
</buz>
</bar>
</foo>
<foo id="2">
<bar id="0">
<baz id="0" blah="blah" etc="etc">
<buz id="0" />
</baz>
<buz id="0" blah="blah" etc="etc">
...
</buz>
</bar>
</foo>
....
What I want to do is to transform this such that, for each foo element, I replace all the zero ids inside it with the id of foo.
To do this - firstly, I'm using Daniel's code along with an implicit conversion to provide 'Elem' with a mapAttributes method:
class ElemWithUsefulAttributes(elem : Elem) extends Elem(elem.prefix, elem.label, elem.attributes, elem.scope, elem.child : _*) {
def mapAttributes(f : GenAttr => GenAttr) = this.copy(attributes = mapMetaData(elem.attributes)(f))
}
implicit def Elem2ElemWithUsefulAttributes(elem : Elem) = new ElemWithUsefulAttributes(elem)
Then I've got a 'replaceId' method:
def replaceId(attr : String, id : String)(in : GenAttr) = in match {
case g@GenAttr(_,key,Text(v),_) if (key.equals(attr)) => g.copy(value=Text(id))
case other => other
}
Finally, I construct a couple of RewriteRule
s and corresponding RuleTransformer
s to deal with this:
class rw1(id : String) extends RewriteRule {
override def transform(n : Node) : Seq[Node] = n match {
case n2: Elem if (n2.label == "bar") => n2.mapAttributes(replaceId("id", id))
case n2: Elem if (n2.label == "baz") => n2.mapAttributes(replaceId("id", id))
case n2: Elem if (n2.label == "buz") => n2.mapAttributes(replaceId("id", id))
case other => other
}
}
class rt1(id : String) extends RuleTransformer(new rw1(id))
object rw2 extends RewriteRule {
override def transform(n : Node) : Seq[Node] = n match {
case n2@Elem(_, "foo", _, _, _*) => (new rw1(n2.attribute("id").get.toString))(n2)
case other => other
}
}
val rt2 = new RuleTransformer(rw2)
After calling rt2(xml)
, I get output that looks like the following:
<foo id="1">
<bar id="1">
<baz id="0" blah="blah" etc="etc">
<buz id="1" />
</baz>
<buz id="0" blah="blah" etc="etc">
...
</buz>
</bar>
</foo>
<foo id="2">
<bar id="2">
<baz id="0" blah="blah" etc="etc">
<buz id="2" />
</baz>
<buz id="0" blah="blah" etc="et开发者_运维技巧c">
...
</buz>
</bar>
</foo>
....
In other words, the attributes haven't been changed where there are multiple attributes. Naturally, one wants to suppose this is a problem with the mapAttributes code - however, if I dump out the results of the transform in the 'match' statement in rw1
, I can clearly see it showing:
<baz id="2" blah="blah" etc="etc">
<buz id="2" />
</baz>
Further, if I alter a single baz, say, to remove the extra attributes then it works correctly and gets mapped. So it seems to be a strange combination of the multiple attributes and the transforms.
Can anybody make head or tail of this?
Shouldn't you be wrapping your new rw1
in a new RuleTransformer
? Otherwise I think it's only going to apply the rule to the foo
node and not its children.
Maybe that alone would solve the problem. In case it doesn't here's some other code that seems to do what you want (using my approach to changing attributes):
class ReplaceIDsBelowFooRule(id: String) extends RewriteRule {
override def transform(n : Node) : Seq[Node] = n match {
case elem: Elem if elem.label == "foo" =>
elem.copy(child=this.replaceDescendantIDs(elem.child))
case other => other
}
def replaceDescendantIDs(nodes: Seq[Node]): Seq[Node] = {
for (node <- nodes) yield node match {
case elem: Elem => elem.copy(
child=this.replaceDescendantIDs(elem.child),
attributes=
for (attr <- elem.attributes) yield attr match {
case attr@Attribute("id", _, _) => attr.goodcopy(value=this.id)
case other => other
}
)
case other => other
}
}
}
And applied to your xml:
scala> new RuleTransformer(new ReplaceIDsBelowFooRule("XXX"))(xml)
res1: scala.xml.Node =
<xml>
<foo id="1">
<bar id="XXX">
<baz blah="blah" etc="etc" id="XXX">
<buz id="XXX"></buz>
</baz>
<buz blah="blah" etc="etc" id="XXX">
...
</buz>
</bar>
</foo>
<foo id="2">
<bar id="XXX">
<baz blah="blah" etc="etc" id="XXX">
<buz id="XXX"></buz>
</baz>
<buz blah="blah" etc="etc" id="XXX">
...
</buz>
</bar>
</foo>
</xml>
The problem might be here:
case other => other
Instead, try:
case elem: Elem => elem.copy(child = transform(elem.child))
case other => other
I'm wondering if the library wasn't broken between 2.7 and 2.8, making transform not recurse automatically into each child. I have to look that up someday (though, of course, other eyes are welcome :).
精彩评论