Xml in Clojure

Here’s the translation of the XML example from Go to Clojure:

(ns xml-example
  (:require [clojure.data.xml :as xml]
            [clojure.string :as str]))

;; Plant will be mapped to XML. We use Clojure's defrecord to define a struct-like entity.
;; The :tag metadata is used to specify the XML element name for the record.
(defrecord Plant [^:tag Plant id name origin])

(defn plant->string [plant]
  (str "Plant id=" (:id plant) ", name=" (:name plant) ", origin=" (:origin plant)))

(defn main []
  (let [coffee (->Plant 27 "Coffee" ["Ethiopia" "Brazil"])]
    
    ;; Emit XML representing our plant
    (let [xml-str (xml/emit-str (xml/sexp-as-element 
                                  [:plant {:id (:id coffee)} 
                                   [:name (:name coffee)]
                                   (map (fn [o] [:origin o]) (:origin coffee))]))]
      (println (str/replace xml-str #">\s*<" ">\n<"))  ;; Add newlines for readability
      
      ;; To add a generic XML header to the output, prepend it explicitly
      (println (str "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" xml-str)))
    
    ;; Use parse-str to parse a string of XML into a data structure
    (let [parsed (xml/parse-str (xml/emit-str (xml/sexp-as-element 
                                                [:plant {:id (:id coffee)} 
                                                 [:name (:name coffee)]
                                                 (map (fn [o] [:origin o]) (:origin coffee))])))]
      (println (plant->string (->Plant 
                                (Integer/parseInt (get-in parsed [:attrs :id]))
                                (first (:content (first (:content parsed))))
                                (map first (rest (:content parsed)))))))
    
    (let [tomato (->Plant 81 "Tomato" ["Mexico" "California"])
          
          ;; In Clojure, we can represent nested XML structure directly as nested vectors
          nesting [:nesting 
                    [:parent
                      [:child
                        (xml/sexp-as-element 
                          [:plant {:id (:id coffee)} 
                           [:name (:name coffee)]
                           (map (fn [o] [:origin o]) (:origin coffee))])
                        (xml/sexp-as-element 
                          [:plant {:id (:id tomato)} 
                           [:name (:name tomato)]
                           (map (fn [o] [:origin o]) (:origin tomato))])]]]]
      
      (println (str/replace (xml/emit-str (xml/sexp-as-element nesting)) 
                            #">\s*<" ">\n<")))))  ;; Add newlines for readability

(main)

This Clojure code demonstrates XML processing using the clojure.data.xml library, which provides functionality similar to Go’s encoding/xml package. Here’s a breakdown of the translation:

  1. We define a Plant record type using defrecord, which is similar to Go’s struct.

  2. The plant->string function is equivalent to the String() method in Go.

  3. In the main function, we create plant instances and demonstrate XML marshalling and unmarshalling.

  4. For XML generation, we use xml/sexp-as-element to create XML structures and xml/emit-str to convert them to strings.

  5. For parsing XML, we use xml/parse-str.

  6. To represent nested XML structures, we use nested vectors, which is idiomatic in Clojure.

  7. We use str/replace with a regex to add newlines to the XML output for better readability.

Note that Clojure’s XML handling is more flexible and data-oriented compared to Go’s struct-based approach. The Clojure version doesn’t require explicit struct tags for XML mapping, as the XML structure is defined directly in the code.

To run this program, save it as xml_example.clj and execute it using a Clojure runtime. The output will be similar to the Go version, showing the generated XML structures and the parsed plant information.