Reputation: 689
I have a xml (stored in the variable report) which looks like this:
<wrapper>
<Sample Id="SomeId1">
<Tag Id="SomeTag">
<Lane Id="1">
[...]
</Lane>
</Tag>
</Sample>
<Sample Id="SomeId2">
<Tag Id="SomeTag">
<Lane Id="1">
[...]
</Lane>
</Tag>
</Sample>
</wrapper>
I want to extract the "Id" attribute from the "Sample" node. I read following article http://www.codecommit.com/blog/scala/working-with-scalas-xml-support and then tried with:
(report \\ "Sample" \ "@Id").text
I got an empty string as return:
scala> (report \\ "Sample" \ "@Id").text
res16: String = ""
But I should have "SomeId1SomeId2" as the return . What have I done wrong?
I found several questions which are similar to mine. Example: Scala: XML Attribute parsing
Upvotes: 3
Views: 1683
Reputation: 139028
If you use the \
selector to pick out an attribute on a NodeSeq
with more than one element, you'll get an empty result, as you can see from the source:
def \(that: String): NodeSeq = {
...
that match {
case "" => fail
case "_" => makeSeq(!_.isAtom)
case _ if (that(0) == '@' && this.length == 1) => atResult
case _ => makeSeq(_.label == that)
}
}
I've wondered about this before, and if I remember correctly I wasn't able to determine that this is documented behavior—I definitely can't find documentation at the moment.
The current implementation at any rate feels like a hack, and leads to some bizarre behavior:
scala> val bar = <bar>{ <baz/>.copy(label = "@baz") }</bar>
bar: scala.xml.Elem = <bar><@baz></@baz></bar>
scala> <foo>{ bar }</foo> \\ "bar" \ "@baz"
res0: scala.xml.NodeSeq = NodeSeq()
scala> <foo>{ bar ++ bar }</foo> \\ "bar" \ "@baz"
res1: scala.xml.NodeSeq = NodeSeq(<@baz></@baz>, <@baz></@baz>)
It's a perverse example, but the result is still pretty horrifying.
As a workaround, I'd personally write something like (report \\ "Sample").flatMap(_ \"@Id")
to get a NodeSeq
of the attribute text elements, and then map text
over that if I needed to.
Upvotes: 2
Reputation: 1010
The following:
(report \ "Sample").head \ "@Id"
results in a NodeSeq containing your attribute. Entering attribute seems to require a single node (unfortunately, I found no documentation on that assumption - reference links welcome)
Upvotes: 0
Reputation: 689
Experimenting some more to this I found an alternative solution to the one provided by @Jean-Philippe Pellet, which I think is slightly more clear (even if I'm sure that there are even better ways to do this.)
report.\\("Sample").foreach(s => println(s.attribute("Id").get.text))
This will return this:
scala> report.\\("Sample").foreach(s => println(s.attribute("Id").get.text))
SomeId1
SomeId2
Since the \ method returns a NodeSeq, one can iterate over each Node and get its attributes and do something with it. In this case just getting them and transforming them to String to print them, but I guess this would allow for more complex operations as well.
Upvotes: 1
Reputation: 59994
I got it working like this:
(xml \\ "Sample").map(n => (n \ "@Id").text)
=> scala.collection.immutable.Seq[String] = List(SomeId1, SomeId2)
but there must be a better solution…
Upvotes: 4