2 votes

Clojure hash-map vers xml

J'essaie de convertir les cartes suivantes en xml (toute clé avec une valeur vectorielle doit répéter la clé dans le xml pour chaque élément du vecteur)

(use 'clojure.xml)
(defn map-to-xml2 [k v]
      (cond
         (nil? k)    (for [[e a] v] {:tag e :content (map-to-xml2 e a)})
         (map? v)    (for [[e a] v] {:tag e :content (map-to-xml2 e a)})
         (vector? v) (for [x v] {:tag k :content (for [[e a] x] {:tag e :content (map-to-xml2 e a)})})
         :else       [(str v)]))

(def studios [{:company {:name "Acme Corp" :id 1 :rank 20 :employee 
                  [{:fname "Mark" :lname "Jones"} {:fname "Leroy" :lname "Bell"}]}}
              {:company {:name "Eastwood Studios" :id 2 :rank 35 :employee 
                  [{:fname "Lee" :lname "Marvin"} {:fname "Clint" :lname "Eastwood"}]}}])

(->> studios first (map-to-xml2 nil) first emit with-out-str (spit "acme.xml"))
(->> studios second (map-to-xml2 nil) first emit with-out-str (spit "eastwood.xml"))

J'obtiens le xml suivant

<?xml version='1.0' encoding='UTF-8'?>
<company>
  <rank>35</rank>
  <employee>
    <employee>
      <lname>Marvin</lname>
      <fname>Lee</fname>
    </employee>
    <employee>
      <lname>Eastwood</lname>
      <fname>Clint</fname>
    </employee>
  </employee>
  <name>Eastwood Studios</name>
  <id>2</id>
</company>

alors que ce que j'ai vraiment besoin d'envoyer via le savon est

<?xml version='1.0' encoding='UTF-8'?>
<company>
  <name>Eastwood Studios</name>
  <id>2</id>  
  <rank>35</rank>
  <employee>
    <lname>Marvin</lname>
    <fname>Lee</fname>
  </employee>
  <employee>
    <lname>Eastwood</lname>
    <fname>Clint</fname>
  </employee>
</company>

Comment puis-je corriger ce qui précède ?

J'essaie de lire les données d'un fichier Excel et, pour chaque ligne ou groupe de lignes ayant un identifiant identique, d'appeler un webservice, puis de mettre à jour la feuille de calcul avec la réponse. Ce qui précède permet de générer le xml nécessaire pour l'appel au webservice.

1voto

Arthur Ulfeldt Points 45059

Votre entrée comporte une couche supplémentaire d'imbrication avec la liste des employés sous le mot-clé :employee dans une carte. Si vous changez cette structure englobante en une liste, vous pouvez aplatir l'arbre entier d'un niveau.

{:company {:name "Acme Corp" :id 1 :rank 20 :employee 
              [{:fname "Mark" :lname "Jones"} {:fname "Leroy" :lname "Bell"}]}}

devient quelque chose comme :

{:company [{:name "Acme Corp"} 
           {:id 1} {:rank 20} 
           {:fname "Mark" :lname "Jones"} 
           {:fname "Leroy" :lname "Bell"}]}

1voto

Omri Bernstein Points 1350

Votre principal problème, que vous connaissez probablement déjà, est que lorsque le contenu (c.-à-d. l'ensemble des données) n'est pas disponible, il n'est pas possible de l'utiliser. v dans le corps du programme) est un vecteur, vous devez faire une sorte de (for ...) o (map ...) pour exprimer véritablement toutes les balises et le contenu de l'expression. Cependant, ce faisant, vous générez un séquence de balises, qui est emballé dans des parens embêtants. D'après ce que je peux dire, il faudrait les "défaire" afin d'obtenir la structure correcte à transmettre à (emit-element ...) .

Ainsi, dans mon code ci-dessous, l'expression (mapcat to-xml ...) se trouve aux endroits où l'imbrication est nécessaire, car elle effectuera l'opération sur des éléments successifs, puis les concaténera tous ensemble. Malheureusement, vous devez alors placer ce qui était auparavant des retours à un seul élément dans des vecteurs (ou des listes si vous le souhaitez). C'est pourquoi lorsque (map? v) est vrai ou lorsque :else se produit, l'ensemble (tag-xml ...) est enveloppée dans un vecteur. Tout retour sera concat avec d'autres retours.

Je pense que j'ai trouvé quelque chose qui va marcher pour toi. Ce n'est pas grand à mon avis, parce que je n'aime pas la façon dont il gère l'appel de niveau supérieur - c'est-à-dire l'appel que vous feriez dans votre code (mais j'y reviendrai plus tard) :

(defn tag-xml
  [tag content]
  {:tag tag
   :content content})

(defn to-xml
  ([[k v]] ;//This form of to-xml is for the sake of nested calls
    (cond
      (map? v) [(tag-xml k (mapcat to-xml v))]
      (vector? v) (for [x v] (tag-xml k (mapcat to-xml x)))
      :else [(tag-xml k [(str v)])]))
  ([k v] ;//This form of to-xml is only for the sake of the top level call
    (tag-xml k (if (map? v) (mapcat to-xml v) [(str v)]))))

Remarquez que j'ai ajouté une fonction d'aide tag-xml . C'était juste pour rendre le corps de to-xml plus propre et plus petit.

C'est ainsi que vous pourriez l'utiliser (bien que dans votre cas, vous remplaceriez println avec quelques spit appel) :

=> (->> studios ffirst (apply to-xml) emit with-out-str println))
<?xml version='1.0' encoding='UTF-8'?>
<company>
<rank>
20
</rank>
<employee>
<lname>
Jones
</lname>
<fname>
Mark
</fname>
</employee>
<employee>
<lname>
Bell
</lname>
<fname>
Leroy
</fname>
</employee>
<name>
Acme Corp
</name>
<id>
1
</id>
</company>
=> nil

Donc, je n'aime pas que pour l'appeler à partir du niveau supérieur correctement, à une carte de hachage existant data vous devez faire (apply to-xml (first data)) . Vous pourrait Pour contourner ce problème, au lieu d'avoir vos données sous forme de carte de hachage, structurez-les comme un vecteur. Dans votre exemple, cela ressemblerait à [:company ...] au lieu de {:company ...} pour chaque studio dans studios . Au lieu de cela, vous pourriez utiliser la fonction comme ceci : (first (to-xml data)) .

Pourtant, ce n'est pas aussi élégant que je l'aurais souhaité. La solution serait peut-être d'avoir une fonction to-xml qui ferait l'appel de niveau supérieur, et une autre fonction -to-xml qui s'en occuperait après ça. En tant qu'utilisateur, vous n'utiliserez que to-xml mais tout le travail difficile serait fait dans -to-xml . Je ne suis pas non plus emballé par cette idée. Une autre idée serait de faire quelque chose un peu comme ce que vous avez fait, où si le premier argument est égal à nil puis il exécute la fonction comme s'il s'agissait d'un appel de niveau supérieur. Hmm.

Enfin, ça marche, c'est déjà ça.

Modifier
Si vous souhaitez conserver l'ordre, vous devrez probablement soit redéfinir vos données, soit les transformer avant de les traiter à l'aide de la fonction to-xml . Vous ne pouvez pas vous fier à l'ordre de tout ce qui est écrit en tant que {...} . Si vous voulez le garder comme une carte, alors vous pourrait obtenir l'ordre en faisant de la carte un tableau ou une carte triée.

Si vous deviez le redéfinir pour en faire un tableau, il ressemblerait à quelque chose comme ceci :

(def studios [(array-map :company (array-map :name "Acme Corp" :id 1 :rank 20
                         :employee [(array-map :fname "Mark" :lname "Jones")
                                    (array-map :fname "Leroy" :lname "Bell")]))
              (array-map :company (array-map :name "Eastwood Studios" :id 2 :rank 35
                         :employee [(array-map :fname "Lee" :lname "Marvin")
                                    (array-map :fname "Clint" :lname "Eastwood")]))])

En gros, partout où vous aviez l'habitude d'avoir {...} vous avez maintenant (array-map ...) . À ce stade, je dois dire qu'il est inutile d'essayer d'écrire une macro pour faire cela à votre place, cela ne fonctionnera pas ( voir ici pour ma question à ce sujet ). Si vous souhaitez utiliser une carte triée, vous devrez créer un comparateur de prédicats qui renvoie simplement les éléments suivants true o false basé sur un ordre codé en dur, et cela me semble un peu étrange.

Maintenant, si vous voulez transformer les données, vous aurez besoin d'une autre structure de données qui contient les ordres clés ainsi que les ordres imbriqués. Quelque chose comme :

(def studio-order-specs {:company [:name :id :rank {:employee [:lname :fname:]}]})

Je n'ai pas la fonction de transformation sous la main, mais en utilisant cette structure de données, vous devriez être capable d'écrire quelque chose qui convertit une carte de hachage en une carte de tableau de l'ordre spécifié. (Vous pourriez également l'utiliser pour convertir en une carte triée de l'ordre spécifié, mais là encore, cela passerait par la définition d'un prédicat de manière inélégante - à mon avis).

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