WPRookie82
WPRookie82

Reputation: 165

Scala: How to append new node to an XML file and how to delete a node from an XML file

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

Answers (1)

Allen Chou
Allen Chou

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

Related Questions