Reputation: 165
I have just started coding in Scala and I am facing difficulties with this very basic XML structure:
<Countries>
<Country>
<Name>Italy</Name>
<Continent>Europe</Continent>
</Country>
<Country>
<Name>Japan</Name>
<Continent>Asia</Continent>
</Country>
</Countries>
Problem 1: I would like to add a new node(a new country) to the file. I successfully loaded the file using XML.loadFile but have no idea how to add the new node and then save back the file.
Problem 2: I would also like to delete nodes from the file but even here I am finding difficulties to achieve what I want especially since I want to delete the node where the element matches the country name inputted by the user.
I used a piece of code I found online:
val removeIt = new RewriteRule {
override def transform(n: Node): NodeSeq = n match {
case e: Elem if (e \ "Name").text == "Japan" => NodeSeq.Empty
case n => n
}
}
This works but unfortunately it returns back a NodeSeq that is not accepted as a parameter of Xml.Save and also I have no idea how to pass a String parameter to determine which node to delete.
Upvotes: 1
Views: 1038
Reputation: 1237
Assuming I put your sample xml code in Country.xml.
<Countries>
<Country>
<Name>Italy</Name>
<Continent>Europe</Continent>
</Country>
<Country>
<Name>Japan</Name>
<Continent>Asia</Continent>
</Country>
</Countries>
The following is How I try to get it done
object XMLLoader extends App {
def toBeAddedEntry(name: String, continent: String) =
<Country>
<Name>{ name }</Name>
<Continent>{ continent }</Continent>
</Country>
// For problem 1 How to add a new Node
def addNewEntry(originalXML: Elem, name: String, continent: String) = {
originalXML match {
case <Countries>{ innerProps @ _* }</Countries> => {
<Countries> {
innerProps ++ toBeAddedEntry(name, continent)
}</Countries>
}
case other => other
}
}
// For problem 2 How to delete node with element Name with certain value
def deleteEntry(originalXML: Elem, nameValue: String) = {
originalXML match {
/*
Considering you just start coding in Scala, the following explanation may help:
Here Elem is used as Extractor, actually the unapplySeq in Elem object is invoked
def unapplySeq(n: Node) = n match {
case _: SpecialNode | _: Group => None
case _ => Some((n.prefix, n.label, n.attributes, n.scope, n.child))
}
Then we use sequence pattern(match against a sequence without specifying how long it can be) to
extract child of originalXML and do the filtering job
*/
case e @ Elem(_, _, _, _, countries @ _*) => {
/*
original is kind of like
<Country>
<Name>Japan</Name>
<Continent>Asia</Continent>
</Country>
*/
val changedNodes = countries filter { country =>
(original \ "Name").exists(elem => elem.text != nameValue)
}
e.copy(child = changedNodes)
}
case _ => originalXML
}
}
// define your own way to load Country.xml
val originalXML = XML.load(getClass.getClassLoader.getResourceAsStream("Country.xml"))
val printer = new scala.xml.PrettyPrinter(80,5)
println(printer.format(addNewEntry(originalXML, "China", "Asia")))
println(printer.format(deleteEntry(originalXML, "Japan")))
}
The result is as following:
<Countries>
<Country>
<Name>Italy</Name>
<Continent>Europe</Continent>
</Country>
<Country>
<Name>Japan</Name>
<Continent>Asia</Continent>
</Country>
<Country>
<Name>China</Name>
<Continent>Asia</Continent>
</Country>
</Countries>
<Countries>
<Country>
<Name>Italy</Name>
<Continent>Europe</Continent>
</Country>
</Countries>
The remaining part is about writing the Node back to Country.xml. Anyway, hope it helps.
Upvotes: 3