2009-11-24 6 views
1

J'analyse XML, et continuer à me trouver un code écrit comme:Scala Newb Question - A propos de la portée et les variables

val xml = <outertag> 
<dog>val1</dog> 
<cat>val2</cat> 
</outertag> 

var cat = "" 
var dog = "" 

for (inner <- xml \ "_") { 
    inner match { 
    case <dog>{ dg @ _* }</dog> => dog = dg(0).toString() 
    case <cat>{ ct @ _* }</cat> => cat = ct(0).toString() 
    } 
} 

/* do something with dog and cat */ 

Il me fâche parce que je devrais être en mesure de déclarer chat et chien comme val (immuable) , puisque j'ai juste besoin de les mettre une fois, mais je dois les rendre mutables. Et à part ça, il semble qu'il devrait y avoir une meilleure façon de faire ça en Scala. Des idées?

Répondre

1

Envisagez d'encapsuler l'inspection XML et la correspondance de modèle dans une fonction qui renvoie les valeurs multiples dont vous avez besoin sous la forme d'un tuple (Tuple2[String, String]). Mais arrêter et de considérer: on dirait qu'il est possible de faire correspondre aucun élément dog et cat, qui vous laissera revenir nulle pour un ou les deux des composants tuple. Peut-être que vous pourriez retourner un tuple de Option[String], ou jeter si l'un des motifs d'éléments ne parviennent pas à se lier.

Dans tous les cas, vous pouvez généralement résoudre ces problèmes d'initialisation en enroulant les instructions qui la composent en fonction pour obtenir une expression. Une fois que vous avez une expression en main, vous pouvez initialiser une constante avec le résultat de son évaluation.

+0

Merci! Pouvez-vous montrer un exemple en utilisant une expression de rendement? Ou au moins une bonne URL qui parle d'eux. Comme je l'ai dit, je suis un newb =) –

+0

Ce n'était pas tout à fait ce que je voulais dire. En définissant une fonction qui renvoie une valeur - ici, un Tuple2 [String, String] - vous avez maintenant une expression disponible, car l'appel d'une fonction qui renvoie une valeur est une expression. Déclarer et initialiser un "val" est lui-même une déclaration, et le membre de droite (la valeur initiale) doit être une expression, pas une déclaration. Cela signifie que le membre de droite peut être une valeur littérale ou une invocation de fonction, mais il ne peut s'agir d'une ou de plusieurs instructions. C'est cette distinction entre les énoncés et les expressions qui encourage souvent à écrire ces petites fonctions. – seh

3

Voici deux (rendent maintenant trois) les solutions possibles. Le premier est assez rapide et sale. Vous pouvez exécuter tout le bit dans l'interpréteur Scala.

val xmlData = <outertag> 
<dog>val1</dog> 
<cat>val2</cat> 
</outertag> 

// A very simple way to do this mapping. 
def simpleGetNodeValue(x:scala.xml.NodeSeq, tag:String) = (x \\ tag).text 

val cat = simpleGetNodeValue(xmlData, "cat") 
val dog = simpleGetNodeValue(xmlData, "dog") 

cat sera "val2", et dog sera "val1".

Notez que si l'un des noeuds est introuvable, une chaîne vide sera retournée. Vous pouvez travailler autour de cela, ou vous pouvez l'écrire d'une manière un peu plus idiomatiques:

// A more idiomatic Scala way, even though Scala wouldn't give us nulls. 
// This returns an Option[String]. 
def getNodeValue(x:scala.xml.NodeSeq, tag:String) = { 
    (x \\ tag).text match { 
    case "" => None 
    case x:String => Some(x) 
    } 
} 

val cat1 = getNodeValue(xmlData, "cat") getOrElse "No cat found." 
val dog1 = getNodeValue(xmlData, "dog") getOrElse "No dog found." 
val goat = getNodeValue(xmlData, "goat") getOrElse "No goat found." 

cat1 sera « val2 », dog1 sera « val1 », et goat sera « pas de chèvre trouvé. »

MISE À JOUR: Voici une méthode plus pratique pour prendre une liste de noms de balises et retourner leurs matchs en tant que carte [String, String].

// Searches for all tags in the List and returns a Map[String, String]. 
def getNodeValues(x:scala.xml.NodeSeq, tags:List[String]) = { 
    tags.foldLeft(Map[String, String]()) { (a, b) => a(b) = simpleGetNodeValue(x, b)} 
} 

val tagsToMatch = List("dog", "cat") 
val matchedValues = getNodeValues(xmlData, tagsToMatch) 

Si vous exécutez que, matchedValues sera Map(dog -> val1, cat -> val2).

Espérons que ça aide! Par la suggestion de Daniel, j'utilise l'opérateur double-backslash, qui descendra dans les éléments enfants, ce qui peut être meilleur au fur et à mesure de l'évolution de votre jeu de données XML.

+0

Je pense que quelqu'un a beaucoup travaillé sur XML ces derniers temps ... :-) Cependant, je vous propose d'utiliser une double barre oblique inversée au lieu d'une simple barre oblique inverse dans le premier exemple. (damned stackoverflow continue de jouer avec les barres obliques inverses) –

+0

Je n'ai pas vraiment beaucoup travaillé avec XML - j'essaie juste d'avoir une idée de Scala. En tout cas, j'essaye de nettoyer les questions plus faciles pour que tu puisses répondre aux doozies, comme une étrange conversion en Python. –

+0

Bon appel sur le double-backslash, soit dit en passant. :) –

2
scala> val xml = <outertag><dog>val1</dog><cat>val2</cat></outertag> 
xml: scala.xml.Elem = <outertag><dog>val1</dog><cat>val2</cat></outertag> 

scala> val cat = xml \\ "cat" text 
cat: String = val2 

scala> val dog = xml \\ "dog" text 
dog: String = val1