2

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.

shscoder
  • 92
  • 1
  • 9

2 Answers2

2

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>
shscoder
  • 92
  • 1
  • 9
  • Removing the prefix is definitively also a solution. If you do not care about the prefix at all, I'd suggest the following generalization: `attributes = a.toList.reverse.foldLeft[MetaData](Null)((next, current) => new UnprefixedAttribute(current.key, current.value, next))` which removes all prefixes, but keeps the attributes the same otherwise. Since everything is immutable, you won't have any bad sideeffects. Conversion to `List` is necessary, because `UnprefixedAttribute` is designed to hold the pointer to the next element itself. – Kulu Limpa Nov 21 '14 at 14:01
  • By the way: If you do not care about some values in your match clause, you can simply use an underscore and don't have to pollute your code with variables. In your code, `case e @ Elem(null, "bar", _, _, _) =>` would match the same. Also, you do not need to add `e @ Elem...`, since `e` here is the same object as `n`, so you could simply cast `n.asInstanceOf[Elem]` instead. – Kulu Limpa Nov 21 '14 at 14:12
1

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.

Kulu Limpa
  • 3,501
  • 1
  • 21
  • 31