sergey-gornostaev
@sergey-gornostaev
Седой и строгий

Как конвертировать xml в csv в функциональном стиле?

Осваиваю новую для себя область - функциональное программирование. Принципы понятны, реализация на Python, Erlang и Scala в основах изучена. А вот перестроение мышления идёт туго. Я слишком стар для этой херни © Примеров в сети немного, да и большинство сугубо теоретического плана.

Есть простая реальная задача - разбирать xml'ку и генерировать на основе её данных csv. Сердцем чую, что функциональный подход здесь весьма кстати, но сообразить как именно это делать не могу.

Структура xml'ка примерно такая:
<group name="">
  <entry name="">
    <item name="" attr1="" attr2="" />
    <item name="" attr1="" attr2="" />
    <item name="" attr1="" attr2="" />
  </entry>
</group>

на выходе хотелось бы получить csv'шку из набора строк вида "group-name;entry-name;attr1-val;attr2-val"

Может кто-нибудь помочь примером?

UPD: Я, видимо, плохо сформулировал вопрос. Я знаю как парсить xml. Я сейчас как раз считываю xml, обхожу в цикле узлы, выполняю необходимые операции со значениями атрибутов, сохраняя промежуточные результаты в словаре, а потом ещё раз обхожу словарь, выполняя постобработку и выгружая результаты в csv. Императивный стиль в каждом решении. Как сделал в функциональном стиле? К чертям циклы и промежуточные данные.
  • Вопрос задан
  • 3873 просмотра
Решения вопроса 1
sergey-gornostaev
@sergey-gornostaev Автор вопроса, куратор тега Clojure
Седой и строгий
xandox хотелось примерно такого
(ns su.gornostaev.rstatfr2csv
  (:use [clojure.xml :only (parse)])
  (:use [clojure.string :only [index-of join split]])
  (:use [clojure.java.io])
  (:import java.io.File))

(defn parse-cmd
  [args]
  (when-let [file-name (first args)]
    file-name))

(defn parse-file
  [file-name]
  (if (nil? file-name) nil (parse (File. file-name))))

(defn format-datetime
  [dt]
  (let [[date time] (split dt #"\s")]
    (str (join "." (reverse (split date #"\."))) " " (subs time 0 (index-of time \:)) ":00")))

(defn merge-counters
  [counters]
  (map
    (fn [item]
      (let [[k & v] item]
      (assoc (reduce (fn [acc val] (merge-with + acc val)) (first v)) :dt k)))
    (reduce
      (fn [acc val] 
        (let [{:keys [dt in out]} val]
        (update-in acc [dt] (fnil #(conj % {:in in :out out}) []))))
      {} counters)))

(defn parse-counters
  [sensor]
  (map
    (fn [counter]
      {:dt (format-datetime (:datetime (:attrs counter))) 
       :in (read-string (:realin (:attrs counter))) 
       :out (read-string (:realout (:attrs counter)))})
    sensor))

(defn parse-sensors
  [shop]
  (map
    (fn [sensor]
      {:name (:name (:attrs sensor)) :counters (merge-counters (parse-counters (:content sensor)))})
    shop))

(defn parse-shops
  [dom]
  (map
    (fn [shop]
      {:name (:name (:attrs shop)) :sensors (seq (parse-sensors (:content shop)))})
    dom))

(defn parse-doc
  [doc]
  (parse-shops
    (->> 
      doc
      :content
      first
      :content)))

(defn save-csv
  [data]
  (with-open [w (writer (file "output.csv"))]
    (doseq [shop data]
      (doseq [sensor (:sensors shop)]
        (doseq [counter (:counters sensor)]
          (.write w (str (join "\t" (concat [(:name shop) (:name sensor)] (vals (into (sorted-map) counter)))) "\n")))))))

(save-csv (parse-doc (parse-file (parse-cmd *command-line-args*))))
Ответ написан
Пригласить эксперта
Ответы на вопрос 3
Wendor
@Wendor
*nix-администратор
preg_match... 3 штуки, потом по результатам - циклы с перебором результатов.

Но лучше взять готовый парсер, к примеру SimpleXML.
Ответ написан
begemot_sun
@begemot_sun
Программист в душе.
По хорошему вам надо посто описать лексер и парсер коьорые в потоковом режиме будут принимать данные из xml и переводить в csv. Никакого промежуточного хранения 100500 мб данных не нужно.

Посмотрите в сторону Erlang, там есть возможность даже встроить ваш xml в исходный код и скомпилировать как модуль.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы