How do I convert Array[Node] to NodeSeq?
Asked Answered
C

3

7

I'm trying to integrate a Lift application into some existing Java code. In one of my snippets, I have an Array of Java objects that I need to map that into a NodeSeq. I can get an Array of Node's, but not a NodeSeq. (At least, not in very functional-looking way).

import scala.xml.NodeSeq

// pretend this is code I can't do anything about
val data = Array("one", "two", "three")

// this is the function I need to write
def foo: NodeSeq = data.map { s => <x>{s}</x> }
//                          ^
// error: type mismatch;
//  found   : Array[scala.xml.Elem]
//  required: scala.xml.NodeSeq

What's the cleanest way to do this?

Clinquant answered 28/10, 2010 at 20:56 Comment(2)
You always have the ++ method available :-) new NodeSeq() ++ theArray_Elm_thing. There may be better ways. Happy coding.Punk
NodeSeq is abstract, so 'new NodeSeq()' doesn't work.Ockham
B
8

I would simply convert map output to sequence (given that Seq[Node] is a super-class of NodeSeq)

scala> def foo: NodeSeq = data.map { s => <x>{s}</x> } toSeq
foo: scala.xml.NodeSeq

or use foldLeft instead of map

scala> def foo: NodeSeq = (Seq[Node]() /: data) {(seq, node)=> seq ++ <x>{node}</x>}
foo: scala.xml.NodeSeq
Bickel answered 29/10, 2010 at 8:57 Comment(3)
toSeq is the elegance I'm looking for. Thanks!Clinquant
Using map and then toSeq means you're creating 2 collections. Using breakOut you're creating 1.Anguilliform
toSeq creates a wrapper(scala-lang.org/api/current/scala/collection/mutable/…), so there's a miserable performance penalty. scala-lang.org/docu/files/collections-api/collections_38.htmlBickel
A
10
scala> import collection.breakOut
import collection.breakOut

scala> def foo: NodeSeq = data.map { s => <x>{s}</x> }(breakOut)
foo: scala.xml.NodeSeq

The method map actually has two argument lists. The first accepts a function, which you passed. The second accepts a CanBuildFrom object which is used to create a builder that then builds the returning sequence. This argument is implicit, so usually the compiler fills it for you. It accepts 3 type parameters: From, T, To. There are several predef implicits (including in object NodeSeq), but none of them matches From=Array, T=Node, To=NodeSeq.

breakOut solves this: it is a generic method that returns a CanBuildFrom instance by searching for an implicit CanBuildFrom[Nothing, T, To]. According to the implicit search rules, any CanBuildFrom that matches T, To and has From > Nothing is acceptable. In this case: canBuildFrom in object Array

Anguilliform answered 28/10, 2010 at 21:2 Comment(1)
It seemed appropriate to link to the breakOut question: https://mcmap.net/q/117337/-scala-2-8-breakoutAvoid
B
8

I would simply convert map output to sequence (given that Seq[Node] is a super-class of NodeSeq)

scala> def foo: NodeSeq = data.map { s => <x>{s}</x> } toSeq
foo: scala.xml.NodeSeq

or use foldLeft instead of map

scala> def foo: NodeSeq = (Seq[Node]() /: data) {(seq, node)=> seq ++ <x>{node}</x>}
foo: scala.xml.NodeSeq
Bickel answered 29/10, 2010 at 8:57 Comment(3)
toSeq is the elegance I'm looking for. Thanks!Clinquant
Using map and then toSeq means you're creating 2 collections. Using breakOut you're creating 1.Anguilliform
toSeq creates a wrapper(scala-lang.org/api/current/scala/collection/mutable/…), so there's a miserable performance penalty. scala-lang.org/docu/files/collections-api/collections_38.htmlBickel
M
2

You are looking for this method on the NodeSeq companion object.

NodeSeq.fromSeq(s: Seq[Node])
Mellette answered 9/3, 2015 at 19:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.