0

Using Scala, I have this code:

def insertRefIntoXml(ref: Int, entry: Node): Node = entry match {
    case <root>{ mainRoot @ _* }</root> 
        => <root>{ mainRoot.map(insertRefIntoXml ref  )}</root>
    case <here>{ contents }</here> => <here>{ ref }</here>
    case other @ _ => other

}

Where what i want to do is keep on passing the "ref" value on down until i get to the here element, and then just swap it in.

This doesn't work. What will?

Check this link for the originating question

Community
  • 1
  • 1
bharal
  • 15,461
  • 36
  • 117
  • 195

2 Answers2

0

See if this works for you:

object TestXml {
  def main(args: Array[String]) {
    val xml = 
      <root>        
    <here>
      <dealID>foo</dealID>
    </here>        
  </root>

    println(insertRefIntoXml(2, xml))
  }

  def insertRefIntoXml(ref: Int, entry: Node): Node = {    
    def doInsertRef(n:Node):Node = {    
      n match {
        case <root>{ mainRoot @ _* }</root> => <root>{ mainRoot.map(doInsertRef)}</root>
        case <here><dealID>{ contents }</dealID></here> => <here><dealID>{ ref }</dealID></here>
        case other @ _ => other
      }       
    }
    doInsertRef(scala.xml.Utility.trim(entry))
  }
}

There were a couple of issues. First, in order to use insertRefIntoXml in a call to map in the way that you wanted to, it need to only have one arg and not two. To fix that, I created a local function def and then get the value from ref in there via closure. You could also solve this problem like this instead:

  def insertRefIntoXml(ref: Int, entry: Node): Node = {       
    entry match {
      case <root>{ mainRoot @ _* }</root> => <root>{ mainRoot.map(insertRefIntoXml(ref, _))}</root>
      case <here><dealID>{ contents }</dealID></here> => <here><dealID>{ ref }</dealID></here>
      case other @ _ => other
    }       
  }

And then call it like this:

insertRefIntoXml(2, scala.xml.Utility.trim(xml))

Which brings me to the second issue. I'm trimming so that the match statements match up correctly. When I run the code, I believe it gives the output you desire.

cmbaxter
  • 35,283
  • 4
  • 86
  • 95
0

The solution would be a zipper structure over the xml tree.

The Anti-XML project used to have that but it seems to have lagged behind so it ships only for scala 2.9.1

Anyway this is how it would work, just in case you would look at the solution to get some inspiration

object XMLTreeUpdate {
  import com.codecommit.antixml._
  import com.codecommit.antixml.Converter

  def insertRefIntoXml(ref: Int, entry: Node, selector: String): Node = {
    /* selects the node of interest, but with the 
     * structure around it to rebuild the original tree (this is a zipper)
     */
    val zipper = Group(entry) \\ selector

    //gets the zipper value and creates a modified copy
    val mod = zipper.head.copy(children = Group(Text(ref.toString)))

    /* updates the modified branch in the zipper and unwinds it
     * then removes the zipper structure and gets the actual result
     */
    zipper.updated(0, mod).unselect.stripZipper.head
  }

  def testIt = {
    val entry = 
      <some>
        <inner>
          this is some content <here>n.d.</here>
          but not <special>here</special>
        </inner>
      </some>

    //converts to antixml node, which has a parallel api to scala.xml
    val antry = entry.convert

    val expected =
      <some>
        <inner>
          this is some content <here>10</here>
          but not <special>here</special>
        </inner>
      </some>

    val output = insertRefIntoXml(10, antry, "here")
    assert(expected.convert == output)
    output
  }
}
pagoda_5b
  • 7,333
  • 1
  • 27
  • 40