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).