How would I focus on only the instances of an element which have a particular attribute value?
This lib looks great!
But I'm a bit stuck on how to do something that I'd expected to be straight forward. Maybe I just don't know enough about optics. What I want is to select only the elements that have an attribute with a given value.
So given this example:
val xml =
"""
|<a>
| <b>
| <c example="1">1234</c>
| <c example="2">5678</c>
| <c example="3">9123</c>
| </b>
|</a>
""".stripMargin
I'd be after only the <c> that has a example attribute with value 2. Using this expression gives me all of the <c> elements:
val c = root \ "b" \ "c"
println(pl.msitko.xml.parsing.XmlParser.parse(xml).map(c.getAll))
//prints:
//Right(List(Element(Vector(Attribute(ResolvedName(,,example),1)),List(Text(1234)),Vector()), Element(Vector(Attribute(ResolvedName(,,example),2)),List(Text(5678)),Vector()), Element(Vector(Attribute(ResolvedName(,,example),3)),List(Text(9123)),Vector())))
I've tried using having, something like this:
val c = root \ "b" \ "c" having {
case LabeledElement(_, Element(attr, _, _)) => attr.find(_.key.localName == "example").exists(_.value == "1")
}
but that seems to only pass child elements of <c> to the partial function, which doesn't give me a chance to inspect the attributes.
Hey @howyp, thank you for your interest in xml-lens and reporting your problem. It's especially appreciated taking into account that this is early version of the lib.
I managed to make it work for your case. First let's define a general Prism[Element, Element] out of given predicate:
def being(predicate: Element => Boolean): Prism[Element, Element] =
Prism[Element, Element]{ el =>
if (predicate(el)) {
Some(el)
} else {
None
}
}(identity)
Then you can define your query as:
val havingAttrValPredicate = ElementOptics.being(el => ElementOptics.attribute("example")
.getOption(el)
.exists(_ == "2"))
val fetch = (root \ "b" \ "c").composePrism(havingAttrValPredicate)
Now you should be able to use such defined fetch on concrete XmlDocument:
fetch.getAll(XmlParser.parse(xml).right.get)
Does it work? Yes. Is it comfortable? No, it's embarrassingly complicated and I will work to make it easier in future. In particular being method should be part of library. The general idea of improving xml-lens is first by defining a set of general lenses (namely pl.msitko.xml.optics package) and then to try to build on top of them a bunch of useful combinators (namely pl.msitko.xml.dsl package). I find the problem you reported very useful for both of objectives.
You can track the progress on this ticket: https://github.com/note/xml-lens/pull/39 if you have any other suggestion you can comment there.
Will close that ticket after above mentioned PR is merged.
I had a similar need and created a fork to work it into the app that I was creating. Here is a link to the pull request: https://github.com/note/xml-lens/pull/44