Search code examples
xmlscalascala-xml

Remove prefixed attribute in scala (scala-xml)


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.


Solution

  • 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.