teo
teo

Reputation: 1403

Transforming an XML element based on its sibling, using scala.xml.transform.RuleTransformer

Given this XML:

<root>
 <item>
   <discriminator>d1</discriminator>
   <target>t1</target>
   <subitem>
       <incomplete></incomplete>
   </subitem>
   <subitem>
       <incomplete></incomplete>
   </subitem>
 </item>
 <item>
    <discriminator>d2</discriminator>
   <target>t2</target>
   <subitem>
       <incomplete></incomplete>
   </subitem>
 </item>
</root>

I need to transform it such that:
1) for each <item>, text of <target> is modified based on the text in <discriminator>.
2) for each <incomplete>, add some text content.

Based on other SO posts, so far I've come up with a solution for 2), using RewriteRule and RuleTransformer. It goes like this:

object completeIncomplete extends RewriteRule {
      override def transform(n: Node): Seq[Node] = n match {
        case Elem(_, "incomplete", _, _, _*) =>
          <incomplete>content</incomplete>
        case other => other
      }
    }
object transform extends RuleTransformer(completeIncomplete)
transform(xml)

This seems to work fine, but I don't know:

For 1), I have tried to pattern match the list of children, like this:

case Elem("", "item", _, _, disc@Elem("", "discriminator", _, _, _*)) 
    if discriminate(disc) => //...?

or this:

case item@Elem("", "item", _, _, _*) 
    if discriminate(item \ "disc") => //..?

But that didn't work out so well, because I don't know how to recreate the whole item with only replacing <target>

I don't even know if in this case matching against <item> is the way to go. But if it is, can I somehow achieve the transformation of its <incomplete>children?

What is the correct approach here?

Upvotes: 0

Views: 293

Answers (1)

cmbaxter
cmbaxter

Reputation: 35463

See if this works for you:

  override def transform(n: Node): Seq[Node] = n match {

    case el @ Elem(_, "item", _, _, _*) =>
      val children = el.child
      val disc = children.find(_.label == "discriminator")
      val content = children.collect{
        case el @ Elem(_, "target", _, _, _*) =>
          <target>{el.text + disc.map(_.text).getOrElse("")}</target>
        case e:Elem => e
      }
      <item>{content}</item>

    case el @ Elem(_, "incomplete", _, _, _*) =>  
      <incomplete>some content</incomplete>
    case other => other
  }

The xml structure is immutable, so when you are on a particular element, you can not access it's parent. Because of that, I chose to approach your problem #1 by stopping on item instead and replacing its child content. I hope this is what you were looking for.

Upvotes: 1

Related Questions