如何在Clojure中将XML转换为edn?
我是 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"]}]}]}
我在转换的初始步骤中遇到了问题。提前谢谢你的帮助。
回答
您拥有的数据是 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 []}]}]})
))
有关
完整信息,请参阅图珀洛森林文档。
上面的代码是使用这个模板项目运行的。
如果您正在寻找分层地图样式输出,您可以像这样拼凑:
(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"}}})))
虽然这很脆弱。