Remove prefixed attribute in scala (scala-xml)

1.7k Views Asked by At

I am trying to remove an attribute with prefix in Scala using a RuleTransformer.

While the following works with unprefixed attributes:

val xml = <foo><bar attr="attval">content</bar></foo>
val rw1 = new RewriteRule {
  override def transform(n: Node) = n match {
    case Elem(null, "bar", a, s, children @ _*) =>
      Elem(null, "bar", a.remove("attr"), TopScope, children: _*)
    case x => x
  }
}
val rt = new RuleTransformer(rw1)
rt(xml)

I do not succeed doing this with a prefixed attribute (note that attribute "attr" of the "bar" element has the prefix "pre"):

val xml = <foo><bar pre:attr="attval">content</bar></foo>
val rw1 = new RewriteRule {
  override def transform(n: Node) = n match {
    case Elem(null, "bar", a, s, children @ _*) =>
      Elem(null, "bar", a.remove("attr"), TopScope, children: _*)
    case x => x
  }
}
val rt = new RuleTransformer(rw1)
rt(xml)

I was trying to use

a.remove("pref",TopScope,"attr")

as defined by

MetaData.remove(namespace: String, scope: NamespaceBinding, key: String)

without any success.

I am a Scala beginner, so bear with me if this is a trivial issue.

2

There are 2 best solutions below

0
On BEST ANSWER

You can't remove a prefixed attribute with remove(String) because of its implementation:

From Attribute.scala:

def remove(key: String) =
  if (!isPrefixed && this.key == key) next
  else copy(next remove key)

As you can see, if the attribute is prefixed, the first branch condition is false. However, there is a different function in the same class:

def remove(namespace: String, scope: NamespaceBinding, key: String) =
  if (this.key == key && (scope getURI pre) == namespace) next
  else copy(next.remove(namespace, scope, key))

where the branch succeeds only if scope.getURI(pre) == namespace, where pre is the prefix, in your example "pre".

The implementation of scope.getURI belongs to NamespaceBinding.scala:

def getURI(_prefix: String): String =
  if (prefix == _prefix) uri 
  else parent getURI _prefix

where all three, prefix, uri, and parent are fields of the class. So in your example, prefix must be "pre", uri must be the string value of namespace which is the first argument to remove, and parent must not be null, if you do not want exceptions to happen.

I do not know much about xml namespaces, but I assume you would need to have the appropriate values if your XML is well defined. If you would want to artificially craft some suitable values for this example, you could do the following (in your transform-method):

case Elem(null, "bar", a, s, children @ _*) =>
  val scope = n.scope.copy(prefix = "pre", uri = "pre", parent = n.scope)
  Elem(null, "bar", a.remove("pre", scope, "attr"), TopScope, minimizeEmpty = true, children: _*)

Note that I set parent to n.scope, which in your example is the top-scope.

A small side-note: the apply-method of Elem that you use is deprecated since Scala 2.10, so I changed it to the non-deprecated method by setting minimizeEmpty to true.

2
On

Just to provide the way how I was able to do the mentioned example thanks to the useful hints given here:

val xml = <foo><bar pre:attr="attval">content</bar></foo>
val rw1 = new RewriteRule {
    override def transform(n: Node) = n match {
      case Elem(null, "bar", _, _, _) =>
        n.asInstanceOf[Elem].copy(attributes = new UnprefixedAttribute("attr", "attval", Null))
    case x => x
  }
}  
val rt = new RuleTransformer(rw1)
rt(xml)

Result:

result: scala.xml.Node = <foo><bar attr="attval">content</bar></foo>