37 votes

Scala - modification des éléments imbriqués en xml

J'apprends scala et je cherche à mettre à jour un nœud imbriqué dans du XML. J'ai quelque chose qui fonctionne, mais je me demande si c'est la manière la plus élégante.

J'ai du xml:

 val InputXml : Node =
<root>
    <subnode>
        <version>1</version>
    </subnode>
    <contents>
        <version>1</version>
    </contents>
</root>
 

Et je veux mettre à jour le nœud de version en sous - nœud , mais pas celui du contenu .

Voici ma fonction:

 def updateVersion( node : Node ) : Node = 
 {
   def updateElements( seq : Seq[Node]) : Seq[Node] = 
   {
    	var subElements = for( subNode <- seq ) yield
    	{
    		updateVersion( subNode )
    	}	
    	subElements
   }

   node match
   {
     case <root>{ ch @ _* }</root> =>
     {
    	<root>{ updateElements( ch ) }</root>
     }
     case <subnode>{ ch @ _* }</subnode> =>
     {
         <subnode>{ updateElements( ch ) }</subnode> 
     }
     case <version>{ contents }</version> =>
     {
        <version>2</version>
     }
     case other @ _ => 
     {
    	 other
     }
   }
 }
 

Existe-t-il une manière plus succincte d'écrire cette fonction?

56voto

Daniel C. Sobral Points 159554

Tout ce temps, et personne n'a donné la réponse la plus appropriée! Maintenant que j'ai appris, même si, voici mon nouveau prendre sur elle:

import scala.xml._
import scala.xml.transform._

object t1 extends RewriteRule {
  override def transform(n: Node): Seq[Node] = n match {
    case Elem(prefix, "version", attribs, scope, _*)  =>
      Elem(prefix, "version", attribs, scope, Text("2"))
    case other => other
  }
}

object rt1 extends RuleTransformer(t1)

object t2 extends RewriteRule {
  override def transform(n: Node): Seq[Node] = n match {
    case sn @ Elem(_, "subnode", _, _, _*) => rt1(sn)
    case other => other
  }
}

object rt2 extends RuleTransformer(t2)

rt2(InputXml)

Maintenant, pour quelques explications. La classe RewriteRule est abstrait. Il définit deux méthodes, tous deux appelés transform. L'un d'eux ne prend qu'un seul Node, de l'autre un Sequence de Node. C'est une classe abstraite, on ne peut pas instancier directement. Par l'ajout d'une définition, dans ce cas remplacer l'un de l' transformméthodes, nous créons un anonyme sous-classe. Chaque RewriteRule besoins concernent lui-même avec une seule tâche, si ça peut faire beaucoup.

Ensuite, la classe RuleTransformer prend comme paramètres un nombre variable d' RewriteRule. C'est transformer méthode prend un Node et de retourner l' Sequence de Node, en appliquant chaque RewriteRule utilisé pour l'instancier.

Les deux classes dérivent d' BasicTransformer, ce qui définit quelques méthodes avec lesquelles on n'a pas besoin de préoccupation de soi à un niveau supérieur. Il est apply des appels de méthode transform, cependant, de sorte que les deux RuleTransformer et RewriteRule pouvez utiliser le sucre syntaxique associé. Dans l'exemple, le premier ne et le, plus tard, ne pas.

Ici, nous utilisons deux niveaux d' RuleTransformer, que le premier s'applique un filtre à la hausse du niveau des nœuds, et la deuxième appliquer la modification à tout ce qui passe le filtre.

L'extracteur Elem est également utilisée, de sorte qu'il n'est pas nécessaire à la préoccupation de soi-même avec des détails tels que les noms, ou si il y a des attributs ou non. Non pas que le contenu de l'élément version est complètement supprimée et remplacée par 2. Il peut être adapté à l'encontre de trop, si nécessaire.

Notez également que le dernier paramètre de l'extracteur est - _*, et pas _. Cela signifie que ces éléments peuvent avoir plusieurs enfants. Si vous oubliez l' *, le match risque d'échouer. Dans l'exemple, le match n'allait pas échouer si il n'y avait pas d'espaces. Parce que les espaces sont convertis en Text éléments, un seul espace sous subnode serait le cas où le match à l'échec.

Ce code est plus grand que les autres suggestions présentées, mais il a l'avantage d'avoir beaucoup moins de connaissances de la structure du XML que les autres. Ça change de tout élément appelé version qui est en dessous, peu importe la façon dont de nombreux niveaux-un élément appelé subnode, peu importe les espaces de noms, attributs, etc.

En outre, " eh bien, si vous avez beaucoup de transformations à faire, récursive filtrage devient rapidement inflexible. À l'aide de RewriteRule et RuleTransformer, vous pouvez remplacer efficacement xslt fichiers avec Scala code.

13voto

David Pollak Points 5756

Vous pouvez utiliser les transformations de sélecteur CSS de Lift et écrire:

 "subnode" #> ("version *" #> 2)
 

Voir http://stable.simply.liftweb.net/#sec:CSS-Selector-Transforms

12voto

GClaramunt Points 2090

Je pense que l'origine de la logique est bonne. C'est le même code (je n'ose dire?) plus de Scala-ish saveur:

def updateVersion( node : Node ) : Node = {
   def updateElements( seq : Seq[Node]) : Seq[Node] = 
     for( subNode <- seq ) yield updateVersion( subNode )  

   node match {
     case <root>{ ch @ _* }</root> => <root>{ updateElements( ch ) }</root>
     case <subnode>{ ch @ _* }</subnode> => <subnode>{ updateElements( ch ) }</subnode>
     case <version>{ contents }</version> => <version>2</version>
     case other @ _ => other
   }
 }

Il a l'air plus compact (mais c'est en fait la même :) )

  1. Je me suis débarrassé de tous les inutiles crochets
  2. Si un support est nécessaire, il commence à de la même ligne
  3. updateElements juste définit une var et la retourne, donc je me suis débarrassé de cette et retourne le résultat directement

si vous le souhaitez, vous pouvez vous débarrasser de la updateElements trop. Vous souhaitez appliquer le updateVersion à tous les éléments de la séquence. C'est la méthode la carte. Avec cela, vous pouvez écrire la ligne

case <subnode>{ ch @ _* }</subnode> => <subnode>{ updateElements( ch ) }</subnode>

avec

case <subnode>{ ch @ _* }</subnode> => <subnode>{ ch.map(updateVersion (_)) }</subnode>

Comme version de mise à jour ne prend que 1 paramètre je suis sûr à 99%, vous pouvez l'omettre et à écrire:

case <subnode>{ ch @ _* }</subnode> => <subnode>{ ch.map(updateVersion) }</subnode>

Et à la fin avec:

def updateVersion( node : Node ) : Node = node match {
         case <root>{ ch @ _* }</root> => <root>{ ch.map(updateVersion )}</root>
         case <subnode>{ ch @ _* }</subnode> => <subnode>{ ch.map(updateVersion ) }</subnode>
         case <version>{ contents }</version> => <version>2</version>
         case other @ _ => other
       }

Qu'en pensez-vous?

6voto

Daniel C. Sobral Points 159554

Depuis, j'ai appris de plus en plus et nous a présenté ce que j'estime être une meilleure solution dans une autre réponse. J'ai aussi corrigé cela, comme j'ai remarqué que j'étais en ne tenant pas compte de l' subnode restriction.

Merci pour la question! J'ai juste appris quelques trucs cool lorsque vous traitez avec XML. Voici ce que vous voulez:

def updateVersion(node: Node): Node = {
  def updateNodes(ns: Seq[Node], mayChange: Boolean): Seq[Node] =
    for(subnode <- ns) yield subnode match {
      case <version>{ _ }</version> if mayChange => <version>2</version>
      case Elem(prefix, "subnode", attribs, scope, children @ _*) =>
        Elem(prefix, "subnode", attribs, scope, updateNodes(children, true) : _*)
      case Elem(prefix, label, attribs, scope, children @ _*) =>
        Elem(prefix, label, attribs, scope, updateNodes(children, mayChange) : _*)
      case other => other  // preserve text
    }

  updateNodes(node.theSeq, false)(0)
}

Maintenant, l'explication. Premier et dernier cas, les déclarations devraient être évidentes. La dernière il en existe un pour rattraper ces pièces d'un XML qui ne sont pas des éléments. Ou, en d'autres termes, le texte. Remarque dans la première déclaration, cependant, le test contre le drapeau pour indiquer si version peut être changé ou pas.

Le deuxième et le troisième cas, les déclarations utiliser un modèle de matcher l'encontre de l'objet Elem. Cela permettra de briser un élément dans toutes ses composantes. Le dernier paramètre, "les enfants @ _*", sera de faire correspondre les enfants à une liste de quoi que ce soit. Ou, plus précisément, Seq[Noeud]. Ensuite, nous reconstruisons l'élément, avec les parties, nous avons extrait, mais passer le Seq[Nœud] pour updateNodes, faire de la récursivité de l'étape. Si nous sommes correspondant à l'encontre de l'élément subnode, puis nous changer le drapeau mayChange d' true, permettant le changement de version.

Dans la dernière ligne, nous utilisons nœud.theSeq pour générer un Seq[Nœud] de Nœud, et (0) pour obtenir le premier élément de la séquence Seq[Nœud] retournée comme résultat. Depuis updateNodes est essentiellement une fonction map (pour ... le rendement est traduit en plan), nous savons que la raison n'ont qu'un seul élément. Nous avons passer un false drapeau pour s'assurer qu'aucun version pourront être modifiés, à moins d'un subnode élément est un ancêtre.

Il y a une manière légèrement différente de le faire, c'est plus puissant mais un peu plus verbeux et obscure:

def updateVersion(node: Node): Node = {
  def updateNodes(ns: Seq[Node], mayChange: Boolean): Seq[Node] =
    for(subnode <- ns) yield subnode match {
      case Elem(prefix, "version", attribs, scope, Text(_)) if mayChange => 
        Elem(prefix, "version", attribs, scope, Text("2"))
      case Elem(prefix, "subnode", attribs, scope, children @ _*) =>
        Elem(prefix, "subnode", attribs, scope, updateNodes(children, true) : _*)
      case Elem(prefix, label, attribs, scope, children @ _*) =>
        Elem(prefix, label, attribs, scope, updateNodes(children, mayChange) : _*)
      case other => other  // preserve text
    }

  updateNodes(node.theSeq, false)(0)
}

Cette version vous permet de modifier tout "version" de la balise, quel que soit son préfixe, attribs et la portée.

3voto

Chris Points 631

Scales Xml fournit des outils pour les modifications "sur place". Bien sûr, tout est immuable, mais voici la solution dans Scales:

 val subnodes = top(xml).\*("subnode"l).\*("version"l)
val folded = foldPositions( subnodes )( p => 
  Replace( p.tree ~> "2"))
 

La syntaxe similaire à XPath est une fonctionnalité de signature Scales, le l après la chaîne spécifie qu'il ne doit pas y avoir d'espace de nom (nom local uniquement).

foldPositions itère sur les éléments résultants et les transforme, réunissant les résultats.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X