您可能已经知道的主要问题是,当内容(即v
在程序主体中)是一个向量,你必须做某种(for ...)
or (map ...)
表达真实地表达它的所有标签和内容。但是,这样做时,您会生成一个sequence标签,它被打包在讨厌的括号内。据我所知,您需要“取消解析”它们才能获得要传递给的正确结构(emit-element ...)
.
因此,在我下面的代码中,表达式(mapcat to-xml ...)
位于需要嵌套的地方,因为这将执行连续项的操作,然后将它们全部连接在一起。不幸的是,您必须将曾经是单个项目返回的内容放入向量(或列表,如果您愿意)内。这就是为什么当(map? v)
为真或当:else
发生,整个(tag-xml ...)
表达式被包裹在向量中。任何回报都将concat
与其他回报一起编辑。
我想我已经找到了适合你的东西。它不是great,在我看来,因为我不喜欢它处理顶级调用的方式——即您将在代码中进行的调用(但我稍后会讨论):
(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)]))))
请注意,我添加了一个辅助函数tag-xml
。这只是为了让身体to-xml
更干净、更小。
这就是您可能使用它的方式(尽管在您的情况下,您将替换println
和一些spit
call):
=> (->> 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
所以,我不喜欢这样从顶层正确调用它到一些现有的哈希映射data
,你需要做(apply to-xml (first data))
. You could为了解决这个问题,不要将数据作为哈希映射,而是将其构造为向量。在你的例子中,这看起来像[:company ...]
代替{:company ...}
对于每个工作室studios
。然后,您可以使用如下所示的函数:(first (to-xml data))
.
尽管如此,这并不像我希望的那样优雅。也许解决方案是有一些功能to-xml
这将执行顶层调用和其他一些功能-to-xml
之后就可以处理了。作为用户,您只会使用to-xml
但所有艰苦的工作都将在-to-xml
。我也不热衷于这个想法。还有一个想法是做一些类似于你所做的事情,如果第一个参数等于nil
然后它就像顶级调用一样执行该函数。唔。
无论如何,它有效,所以就是这样。
Edit
至于想要保留顺序,您可能必须重新定义数据,或者在处理它之前对其进行转换to-xml
。你不能依赖任何写成的顺序{...}
。如果您想将其保留为地图,那么您could通过将其设为数组映射或排序映射来获取顺序。
如果您要重新定义它以使其成为数组映射,它将看起来像这样:
(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")]))])
基本上,任何你曾经去过的地方{...}
你现在有(array-map ...)
。在这一点上,我应该说,不要费心尝试编写一个宏来为你做到这一点,它不会起作用(请参阅此处了解我对此的问题 https://stackoverflow.com/questions/11130460/clojure-macro-that-will-conserve-associative-map-order)。如果您想使用排序映射,则必须创建一个仅返回的谓词比较器true
or false
基于一些硬编码的顺序,这对我来说似乎有点奇怪。
现在,如果您想转换数据,您将需要另一个包含关键订单和嵌套订单的数据结构。就像是:
(def studio-order-specs {:company [:name :id :rank {:employee [:lname :fname:]}]})
我手头没有转换函数,但是使用这个数据结构,您应该能够编写一些将哈希映射转换为指定顺序的数组映射的东西。 (您也可以使用它来转换为指定顺序的排序映射,但同样,在我看来,这将是通过以不优雅的方式定义谓词来实现。)