Michael
Michael

Reputation: 42100

How to do escaping idiomatically in Scala

Suppose I am writing a simple function to escape XML string.

val map = Map('&' -> "&amp;", '<' -> "&lt;", '>' -> "&gt;") // ... and more
def escape(str: String): String = {
  val w = new java.io.StringWriter()
  for(c <- str) if (map.contains(c)) w.write(map(c)) else w.write(c); 
  w.toString
}

It does not look idiomatically in Scala and besides, I do not know how to deal with the map, which maps characters to escaping strings. How would you suggest me fix it?

Upvotes: 0

Views: 1055

Answers (3)

Erik Kaplun
Erik Kaplun

Reputation: 38247

Real life solution

Just use scala.xml.Utility.escape:

scala> scala.xml.Utility.escape(foo)
res7: String = &amp;foo&lt;bar&gt;hello

If you're still interested in doing it yourself:

First of all, there's no need to repeat the & and ; parts in the escapes map:

scala> val Escapes = Map('&' -> "amp", '<' -> "lt", '>' -> "gt")

(it's simpler and faster to have them in tho... but since you seem to have asked this question for learning purposes...)

scala> def escapeChar(c: Char) = Escapes.get(c).map { x => s"&$x;" }
escapeChar: (c: Char)Option[String]

scala> def escapeStr(s: String) = s.flatMap { c => escapeChar(c).getOrElse(c.toString) }
escapeStr: (s: String)String

scala> escapeStr("&foo<bar>hello")
res9: String = &amp;foo&lt;bar&gt;hello

...you could also just inline the escapeChar(c: Char) function, but I think it's more readable this way.

In case you're interested: this works by treating the string as a sequence of chars; you flatMap over it, which allows you to map each char into more than one char (a String); flatMap then joins all the emitted strings into a single string. Characters that don't need escaping are trivially mapped to one-char strings.

Upvotes: 10

om-nom-nom
om-nom-nom

Reputation: 62855

Just for a sake of completeness -- basically the same solution, but with function method in place of the map:

def escape(char: Char) = char match {
     case '&' => "&amp;"
     case '<' => "&lt;"
     case '>' => "&gt;"
     case noEscaping  => noEscaping.toString 
}
val str = "hit & run"
str.flatMap(escape)
// hit &amp; run

Upvotes: 5

Eastsun
Eastsun

Reputation: 18869

How about this:

scala> val map = Map[Char, Seq[Char]]('&' -> "&amp;", 
       '<' -> "&lt;", 
       '>' -> "&gt;").withDefault(x => Seq(x))
map: scala.collection.immutable.Map[Char,Seq[Char]] = Map(& -> &amp;, < -> &lt;, > -> &gt;)

scala> "&foo<bar>hello".flatMap(map)
res2: String = &amp;foo&lt;bar&gt;hello

OR

scala> val map = Map('&' -> "&amp;", '<' -> "&lt;", '>' -> "&gt;").withDefault(identity)

scala> "&foo<bar>hello".map(map).mkString
res3: String = &amp;foo&lt;bar&gt;hello

Upvotes: 4

Related Questions