5

do you know any Scala API to insert and (or) update Nodes according to XPath? e.g for a given Node and XPath, this API would create a copy of XML with new node

thanks

user1746915
  • 331
  • 1
  • 3
  • 8
  • Xpath is a query language. As far as I remember you can't update or write with it. Do you mean xslt? – Jan Oct 15 '12 at 11:52
  • XPath will be used to "select" a node and you provide a node that will be added as a child to this "parent" nod. btw. I know, that Scala XML is immutable, but is there any possible way, how to accomplish this task? – user1746915 Oct 15 '12 at 12:02
  • What about [this](http://stackoverflow.com/questions/4018300/substituting-xml-values-programatically-with-scala) question? It shows how to update a node with scala xml... – Jan Oct 15 '12 at 12:06
  • In Scala's XML library nodes don't point to their parents, which makes the kind of thing you describe tricky. The [Anti-XML](http://anti-xml.org/) library provides this functionality through [zippers](http://en.wikipedia.org/wiki/Zipper_%28data_structure%29), where you use an XPath-like operator to navigate into the document, make your edits, and then `unselect` to get the edited document—see e.g. [this answer](http://stackoverflow.com/a/10421660/334519) for an example. – Travis Brown Oct 15 '12 at 15:22
  • 3
    @TravisBrown: can that be an answer? Then this q wouldn't be categorized as "unanswered." – LarsH Oct 15 '12 at 17:36

2 Answers2

9

You can use the RewriteRule to do this, 2.10.3 documentation.

val cats = <Cats>
  <Cat Name="Floyd"/>
  <Cat Name="Onyx"/>
</Cats>

Then suppose the RewriteRule

class AddCat(name: String) extends RewriteRule {
   override def transform(n: Node): Seq[Node] = n match {
     case e: Elem if e.label == "Cats" =>
       val cats = (e \\ "Cat")
       val newCat = <Cat Name={name}/>
       new Elem(e.prefix, "Cats", e.attributes, e.scope, e.minimizeEmpty, (cats ++ newCat).toSeq:_*)
     case x => x
   }
 }

Then you could do,

val rule = new RuleTransformer(new AddCat("Stevie"))
rule.transform(cats)
res2: Seq[scala.xml.Node] = List(<Cats><Cat Name="Floyd"/><Cat Name="Onyx"/><Cat Name="Stevie"/></Cats>)

Similarly if you wanted to change an attribute

class AddLastName(name: String, lastName: String) extends RewriteRule {
  override def transform(n: Node): Seq[Node] = n match {
    case e: Elem if e.label == "Cat" && (e \\ "@Name" text).equals(name) =>
      val cat: String = e.attributes("Name").head.text
      e % Attribute(None, "Name", Text(s"$name $lastName"), Null)
    case x => x
  }
}

val rule = new RuleTransformer(new AddLastName("Stevie", "Nicks"))
rule.transform(cats)
res3: Seq[scala.xml.Node] = List(<Cats><Cat Name="Floyd"/><Cat Name="Onyx"/><Cat Name="Stevie Nicks"/></Cats>)

Both of these approaches would do what you're looking for. The hard part is figuring out how to get at the children, then build the parent node.

tysonjh
  • 1,329
  • 1
  • 12
  • 27
  • 1
    This is one of the best answers out here on SO. I can't believe it does not get more upvotes and did not get marked as correct. I have looked and looked at many others, but this was the most helpful. Thanks a lot @tysonjh. – Philippe Aug 04 '16 at 21:05
0

What I would do is parse the xml using \\

convert the xml to a list using .toList.last.text

append what I need to and then collect my options: jsonOption collect here you can change some options again using orElse Some and then finally use a for yield to yield the xml I want.

Take advantage of scale's flexibility in data conversion. It doesn't have to be identical but I'm sure you can see the light...