【问题标题】:How to convert XML to edn in Clojure?如何在 Clojure 中将 XML 转换为 edn?
【发布时间】:2021-05-07 00:55:25
【问题描述】:

我是 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"]}]}]}

我在转换的初始步骤中遇到了问题。提前感谢您的帮助。

【问题讨论】:

    标签: xml clojure type-conversion converters edn


    【解决方案1】:

    您拥有的数据是 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 docs 了解完整信息。


    上面的代码是使用this template project运行的。


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

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

    虽然这写起来很脆弱。

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • Enlive 使用与 clojure.xml 相同的数据表示形式,从 1.0 版开始就成为 Clojure 的一部分。
      • 你说的很对,clojure.xml 最先使用了这种格式。我已经修改了我的答案,说 Enlive 普及了它(直到 1.2 出现时我才出现,但即使是那个遥远的 clojure.xml 也不是流行的工具)。
      猜你喜欢
      • 2020-11-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-19
      • 1970-01-01
      • 2013-11-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多