2

我正在尝试将以下映射转换为 xml(具有向量值的任何键都需要为向量中的每个元素重复 xml 中的键)

(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"))

我得到以下xml

<?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>

当我真正需要通过肥皂发送的是

<?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>

我如何更正上述内容?

我正在尝试从 excel 文件中读取数据,并为具有相同 id 的每一行或每组行进行 web 服务调用,然后使用响应更新电子表格。以上就是生成webservice调用需要的xml。

4

2 回答 2

1

您的输入与地图中 :employee 关键字下的员工列表有一层额外的嵌套。如果将该封闭结构更改为列表,则可以将整个树展平一层。

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

变成这样:

{:company [{:name "Acme Corp"} 
           {:id 1} {:rank 20} 
           {:fname "Mark" :lname "Jones"} 
           {:fname "Leroy" :lname "Bell"}]}
于 2012-07-30T22:47:20.020 回答
1

您可能已经知道的主要问题是,当内容(即v在程序主体中)是向量时,您必须进行某种(for ...)(map ...)表达式才能真正表达它的所有标签和内容。但是,这样做会生成一系列标签,这些标签被打包在讨厌的括号内。据我所知,您需要“取消”那些以获得正确的结构以传递给(emit-element ...).

因此,在我下面的代码中,表达式(mapcat to-xml ...)位于需要嵌套的地方,因为这将执行连续项的操作,然后将它们连接在一起。不幸的是,您必须将过去的单项返回值放入向量(或列表,如果需要)。这就是为什么当何时(map? v)为真或何时:else发生时,整个(tag-xml ...)表达式被包裹在一个向量中。任何退货都将concat与其他退货一起使用。

我想我找到了适合你的东西。在我看来,这不是很好,因为我不喜欢它处理顶级调用的方式——即您将在代码中进行的调用(但我稍后会谈到):

(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电话代替):

=> (->> 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

所以,我不喜欢这样,为了从顶层正确调用它,到一些现有的 hash-map data,你需要做(apply to-xml (first data)). 您可以通过将数据构造为向量而不是将数据作为哈希映射来解决此问题。在你的例子中,这看起来像[:company ...]而不是{:company ...}每个工作室studios。然后,您可以使用这样的函数:(first (to-xml data)).

尽管如此,这并不像我希望的那样优雅。也许解决方案是拥有一些to-xml可以进行顶级调用的函数,以及-to-xml之后处理它的一些其他函数。作为用户,您只会使用to-xml,但所有艰苦的工作都将在-to-xml. 我也不喜欢这个想法。另一个想法是做一些有点像你所做的事情,如果第一个参数等于,nil那么它就像一个顶级调用一样执行函数。唔。

无论如何,它有效,所以就是这样。

编辑
至于想要保留订单,您可能必须重新定义您的数据,或者在使用to-xml. 你不能依赖任何写成的顺序{...}。如果要将其保留为地图,则可以通过将其设为数组地图或排序地图来获得订单。

如果您要重新定义它以使其成为数组映射,它将如下所示:

(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 ...). 在这一点上,我应该说,不要费心尝试编写一个宏来为你做这件事,它不会起作用(关于我的问题,请参见这里)。如果您想使用排序映射,则必须创建一个谓词比较器,该比较器仅返回truefalse基于一些硬编码的顺序,这对我来说似乎有点奇怪。

现在,如果要转换数据,您将需要另一个包含关键订单和嵌套订单的数据结构。就像是:

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

我手头没有转换函数,但是使用这个数据结构,您应该能够编写一些东西来将哈希映射转换为指定排序的数组映射。(您也可以使用它来转换为指定顺序的排序映射,但同样,这将是通过以一种不优雅的方式定义谓词——在我看来。)

于 2012-07-31T14:29:53.457 回答