Johan
Johan

Reputation: 689

Selecting a xml attribute

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

Answers (5)

Travis Brown
Travis Brown

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

Skyr
Skyr

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

Johan
Johan

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

Jean-Philippe Pellet
Jean-Philippe Pellet

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

Phebus40
Phebus40

Reputation: 173

You have to use [] instead of \

\\"Sample"[@Id]

Upvotes: -3

Related Questions