2

我是 Clojure 的新手,想将我拥有的 XML 转换为 edn 对象。

我读取的 XML 文件:

<Vehicle>
  <Model>Toyota</Model>
  <Color>Red</Color>
  <Loans>
    <Reoccuring>Monthly</Reoccuring>
    <Owners>
      <Owner>Bob</Owner>
    </Owners>
  </Loans>
  <Tires>
    <Model>123123</Model>
    <Size>23</Size>
  </Tires>
  <Engine>
    <Model>30065</Model>
  </Engine>
</Vehicle>

我已将其保存为 'test/resources/vehicle.xml

最终,我希望有一个如下所示的 EDN 对象:

:Vehicle
    :Model "Toyota"
    :Color "Red"
    :Loans
        :Reoccuring "Monthly"
        :Owners
            :Owner "Bob"
    :Tires
        :Model 123123
        :Size 23
    :Engine
        :Model 30065

到目前为止,我在 Clojure 中尝试过的是 parse 方法:

(def xml-parser
  (parse "<Vehicle><Model>Toyota</Model><Color>Red</Color><Loans><Reoccuring>Monthly</Reoccuring><Owners><Owner>Bob</Owner></Owners></Loans><Tires><Model>123123</Model><Size>23</Size></Tires><Engine><Model>30065</Model></Engine></Vehicle>"))

但是,这会返回一个 Clojure 哈希,如下所示:

{:tag :Vehicle, :attrs nil, :content [{:tag :Model, :attrs nil, :content ["Toyota"]} {:tag :Color, :attrs nil, :content ["Red"]} {:tag :Loans, :attrs nil, :content [{:tag :Reoccuring, :attrs nil, :content ["Monthly"]} {:tag :Owners, :attrs nil, :content [{:tag :Owner, :attrs nil, :content ["Bob"]}]}]} {:tag :Tires, :attrs nil, :content [{:tag :Model, :attrs nil, :content ["123123"]} {:tag :Size, :attrs nil, :content ["23"]}]} {:tag :Engine, :attrs nil, :content [{:tag :Model, :attrs nil, :content ["30065"]}]}]}

我在转换的初始步骤中遇到了麻烦。提前谢谢你的帮助。

4

2 回答 2

5

您拥有的数据是 Enlive 格式的。用于clojure.pprint/pprint查看更好的格式:

{:tag :Vehicle,
 :attrs nil,
 :content
 [{:tag :Model, :attrs nil, :content ["Toyota"]}
  {:tag :Color, :attrs nil, :content ["Red"]}
  {:tag :Loans,
   :attrs nil,
   :content
   [{:tag :Reoccuring, :attrs nil, :content ["Monthly"]}
    {:tag :Owners,
     :attrs nil,
     :content [{:tag :Owner, :attrs nil, :content ["Bob"]}]}]}
  {:tag :Tires,
   :attrs nil,
   :content
   [{:tag :Model, :attrs nil, :content ["123123"]}
    {:tag :Size, :attrs nil, :content ["23"]}]}
  {:tag :Engine,
   :attrs nil,
   :content [{:tag :Model, :attrs nil, :content ["30065"]}]}]}

问题是您想要的输出实际上不是合法的 EDN 数据格式。但是,您可以使用该tupelo.forest库在多种数据格式之间进行转换:

首先声明数据并解析成Enlive格式:

(ns tst.demo.core
  (:use tupelo.core tupelo.test)
  (:require
    [tupelo.parse.xml :as xml]
    [tupelo.forest :as tf])
  )

(def xml-str
   "<Vehicle>
      <Model>Toyota</Model>
      <Color>Red</Color>
      <Loans>
        <Reoccuring>Monthly</Reoccuring>
        <Owners>
          <Owner>Bob</Owner>
        </Owners>
      </Loans>
      <Tires>
        <Model>123123</Model>
        <Size>23</Size>
      </Tires>
      <Engine>
        <Model>30065</Model>
      </Engine>
    </Vehicle> ")

验证结果

(dotest
  (let [data-enlive (xml/parse xml-str)]
    (is= data-enlive
      {:tag     :Vehicle,
       :attrs   {},
       :content [{:tag :Model, :attrs {}, :content ["Toyota"]}
                 {:tag :Color, :attrs {}, :content ["Red"]}
                 {:tag     :Loans,
                  :attrs   {},
                  :content [{:tag :Reoccuring, :attrs {}, :content ["Monthly"]}
                            {:tag     :Owners,
                             :attrs   {},
                             :content [{:tag :Owner, :attrs {}, :content ["Bob"]}]}]}
                 {:tag     :Tires,
                  :attrs   {},
                  :content [{:tag :Model, :attrs {}, :content ["123123"]}
                            {:tag :Size, :attrs {}, :content ["23"]}]}
                 {:tag     :Engine,
                  :attrs   {},
                  :content [{:tag :Model, :attrs {}, :content ["30065"]}]}]})

转换为打嗝格式:

    (is= (tf/enlive->hiccup data-enlive)
      [:Vehicle
       [:Model "Toyota"]
       [:Color "Red"]
       [:Loans [:Reoccuring "Monthly"]
        [:Owners [:Owner "Bob"]]]
       [:Tires [:Model "123123"]
        [:Size "23"]]
       [:Engine [:Model "30065"]]])

您可能还喜欢“灌木”格式:

    (is= (tf/enlive->bush data-enlive)
      [{:tag :Vehicle}
       [{:tag :Model, :value "Toyota"}]
       [{:tag :Color, :value "Red"}]
       [{:tag :Loans}
        [{:tag :Reoccuring, :value "Monthly"}]
        [{:tag :Owners} [{:tag :Owner, :value "Bob"}]]]
       [{:tag :Tires}
        [{:tag :Model, :value "123123"}]
        [{:tag :Size, :value "23"}]]
       [{:tag :Engine} [{:tag :Model, :value "30065"}]]])

或更详细的“树”格式

    (is= (tf/enlive->tree data-enlive)
      {:tag :Vehicle,
       :tupelo.forest/kids
            [{:tag :Model, :value "Toyota", :tupelo.forest/kids []}
             {:tag :Color, :value "Red", :tupelo.forest/kids []}
             {:tag :Loans,
              :tupelo.forest/kids
                   [{:tag :Reoccuring, :value "Monthly", :tupelo.forest/kids []}
                    {:tag :Owners,
                     :tupelo.forest/kids
                          [{:tag :Owner, :value "Bob", :tupelo.forest/kids []}]}]}
             {:tag :Tires,
              :tupelo.forest/kids
                   [{:tag :Model, :value "123123", :tupelo.forest/kids []}
                    {:tag :Size, :value "23", :tupelo.forest/kids []}]}
             {:tag :Engine,
              :tupelo.forest/kids
                   [{:tag :Model, :value "30065", :tupelo.forest/kids []}]}]})
    ))

有关完整信息,请参阅Tupelo Forest 文档


上面的代码是使用这个模板项目运行的。


如果您正在寻找分层地图样式的输出,您可以将这样的东西拼凑在一起:

(ns tst.demo.core
  (:use tupelo.core tupelo.test)
  (:require [clojure.walk :as walk]))

(dotest
  (let [data  [:Vehicle
               [:Model "Toyota"]
               [:Color "Red"]
               [:Loans
                [:Reoccuring "Monthly"]
                [:Owners
                 [:Owner "Bob"]]]
               [:Tires
                [:Model "123123"]
                [:Size "23"]]
               [:Engine
                [:Model "30065"]]]

        mappy (walk/postwalk
                (fn [item]
                  (if (vector? item)
                    (if (= 2 (count item))
                      (conj {} item)
                      {(first item)
                       (into {} (rest item))})
                    item))
                data)]

有测试

    (is= mappy
      {:Vehicle
       {:Model  "Toyota",
        :Color  "Red",
        :Loans  {:Reoccuring "Monthly"
                 :Owners     {:Owner "Bob"}},
        :Tires  {:Model "123123"
                 :Size  "23"},
        :Engine {:Model "30065"}}})))

虽然这是非常脆弱的书面。

于 2021-02-02T20:27:14.827 回答
3

您得到的输出是在 Clojure 中建模 XML 文档的最灵活的方法。正如 Alan Thompson 所说,它通常被称为 Enlive,因为它是enlive推广这种模型的库。目前尚不清楚您还希望得到什么,因为您的预期输出只是一堆没有结构的关键字。您可能一直希望 Hiccup 样式(再次在 Alan Thompson 的回答中描述)或嵌套地图(Alan Thompson 的回答称为“mappy”),但如果是这样,我敦促您重新考虑。Hiccup 的唯一优点是易于手写。Enlive 更容易消费和转换。

mappy 格式使用起来非常方便,但遗憾的是它与 XML 文档并没有 1:1 对应,因为那些可能有重复的元素名称,并且因为 mappy 没有办法描述属性,只能描述元素。因此,XML 解析器无法在不丢失保真度的情况下将其作为输出格式提供。如果你知道你的输入没有任何这些问题,你可以自己从 Enlive 编写一个转换函数——对于一个固定的模式来说这很容易。

于 2021-02-02T22:36:34.213 回答