Dylan Knowles
Dylan Knowles

Reputation: 2803

Any way to extract XML into constants in Scala?

Beginner to Scala here coming from a Java background. Suppose I have a bunch of XML just floating around in my code:

val x = <file> <name> some-file.txt </name> </file>

Is there any way I can extract the XML elements into named constants? I've tried the following, but it doesn't work as on the first line it's still expecting a closing </file> tag:

val FileStart = <file>
val FileEnd = </file>

I ask because I want to avoid magic values floating around in my code. I cringe at the thought of using the <name> tag 100 times and then having its value change ten months down the road (which would result in a terrible tag hunt). I'd much rather have it defined in a constant somewhere.

Better yet, is there a Scala way to efficiently approach this problem? Maybe I'm stuck in Java thinking.

Upvotes: 1

Views: 226

Answers (2)

som-snytt
som-snytt

Reputation: 39587

Extraction:

scala> val x = <file> <name> some-file.txt </name> </file>
x: scala.xml.Elem = <file> <name> some-file.txt </name> </file>

scala> import xml.Elem
import xml.Elem

scala> x match { case Elem(prefix, label, attrs, ns, children @ _*) => println(label) }
file

scala> val File = "file"
File: String = file

scala> x match { case Elem(prefix, File, attrs, ns, children @ _*) => println(children) } 
ArrayBuffer( , <name> some-file.txt </name>,  )

scala> x match { case Elem(prefix, File, attrs, ns, children @ _*) => children \\ "name" }
res4: scala.xml.NodeSeq = NodeSeq(<name> some-file.txt </name>)

File must not start with a lower-case letter, otherwise it is a variable.

Well, that's if you're extracting nodes without using literal tags in the pattern.

If you want to construct nodes without using literals, you can do that, too:

scala> val n = <name> some-file.txt </name>
n: scala.xml.Elem = <name> some-file.txt </name>

scala> val file = "file"
file: String = file

scala> import xml._
import xml._

scala> val f = Elem(null, file, Null, TopScope, minimizeEmpty = false, n)
f: scala.xml.Elem = <file><name> some-file.txt </name></file>

Here is a nice syntax:

https://github.com/lihaoyi/scalatags#hello-world

That focuses on producing html text.

scala> val tag = "file"
tag: String = file

scala> val file: Node => Node = Elem(null, tag, Null, TopScope, true, _)
file: scala.xml.Node => scala.xml.Node = <function1>

scala> file { <name> some-file.txt </name> }
res0: scala.xml.Node = <file><name> some-file.txt </name></file>

Upvotes: 1

Norbert Radyk
Norbert Radyk

Reputation: 2618

Your suggestion is not a good one, as the following:

val FileStart = <file>
val FileEnd = </file>

doesn't create and assign to a variable a valid XML nodes, which is required and validated during compilation. Even more unexpectedly Scala compiler will interpret the above as definition of a single node FileStart, as a <file>val FileEnd = </file> XML node ;)

Since you're worried of having multiple name tags for which you might need to change the node name, I'd rather suggest defining a set of separate reusable methods i.e.:

def nameNode(name : String) = <name>{name}</name>

def fileNode(name : String) = <file>{nameNode(name)}</file>

def createXml(files : List[String]) = <myLovelyFiles>
  {files.map(fileNode)}
</myLovelyFiles>

This way you will not only avoid duplicating tag name 100 times, but also should you need to test some complex functionality when constructing XML you've got a small testable units for it.

Hope this helps.

Upvotes: 4

Related Questions