Как удалить пустые теги XML с Clojure.data.xml? - PullRequest
0 голосов
/ 05 декабря 2018

Учитывая пространство имен xml (игнорируется в этом примере)

<foo>
    <name>John</name>
    <address>1 hacker way</address>
    <phone></phone>
    <school>
        <name></name>
        <state></state>
        <type></type>
    </school>
    <college>
        <name>mit</name>
        <address></address>
        <state></state>
    </college>
</foo>

как бы вы написали функцию, remove-empty-tags с clojure.data.xml , чтобы вернуть следующее?

<foo>
  <name>John</name>
  <address>1 hacker way</address>
  <college> 
    <name>mit</name>
  </college>
</foo>

Мое решение до сих пор не завершено и похоже, что некоторая рекурсия может помочь:

(require '[clojure.data.xml :as xml])

(defn- child-element? [e]
  (let [content (:content e)]
    (and (= (count content)
            (count (filter #(instance? clojure.data.xml.node.Element %) content))))))


(defn remove-empty-tags
  [xml-data]
  (let [empty-tags? #(or (empty? %) (-> % .toString blank?))]
    (reduce (fn [col e]
               (if-not (empty-tags? (:content e))
                 (merge col e)
                  col)))
            xml-data))

(def body (slurp "sample.xml")) ;; the above xml
(def xml-data (-> (xml/parse (java.io.StringReader. body)) :content))

(remove-empty-tags xml-data)

Возвращается после преобразования в xml:

<foo>
    <name>John</name>
    <address>1 hacker way</address>
    <school>
        <name/>
        <state/>
    </school>
    <college>
        <name>mit</name>
        <address/>
        <state/>
    </college>
</foo>

Очевидно,эта функция должна быть рекурсивной для удаления пустых дочерних узлов, используя child-element?.

Предложения?

Ответы [ 3 ]

0 голосов
/ 05 декабря 2018

Вы можете легко управлять древовидными структурами данных, используя библиотеку Tupelo Forest .Вот видео с Clojure Conj 2017 года, которое дает представление.Для вашей проблемы:

  (let [xml-data "<foo>
                  <name>John</name>
                  <address>1 hacker way</address>
                  <phone></phone>
                  <school>
                      <name></name>
                      <state></state>
                      <type></type>
                  </school>
                  <college>
                      <name>mit</name>
                      <address></address>
                      <state></state>
                  </college>
                </foo> "]

Мы добавляем данные xml в новый лес и удаляем все пробельные узлы:

  (with-forest (new-forest)
    (let [root-hid (add-tree-xml xml-data)]
      (remove-whitespace-leaves)

с результатом:

(hid->hiccup root-hid) => 

    [:foo
     [:name "John"]
     [:address "1 hacker way"]
     [:phone]
     [:school [:name] [:state] [:type]]
     [:college [:name "mit"] [:address] [:state]]]

Мы можем пройтись по дереву и удалить пустые узлы следующим образом:

      (walk-tree root-hid {:leave (fn [hid]
                                    (when (empty-leaf-hid? hid)
                                      (remove-hid hid)))})

с результатом:

(hid->hiccup root-hid) =>

     [:foo 
       [:name "John"]
       [:address "1 hacker way"]
       [:college 
        [:name "mit"]]]

Обновление

живой код можно увидеть здесь .


Обновление # 2

Если вы хотите запустить код, вам нужно что-токак в форме ns (см. пример реального кода выше):

(ns tst.tupelo.forest-examples
  (:use tupelo.core tupelo.forest tupelo.test)
  ...)
0 голосов
/ 10 декабря 2018

Мне удалось достичь этого с помощью комбинации рекурсии и сокращения (мой первоначальный частичный ответ, полный).Ключ должен был передать заголовок каждого узла в рекурсии, так что Reduce может прикрепить преобразование дочерних узлов к заголовку.

(defn- child-element? [e]
    (let [content (:content e)]
      (and (= (count content)
              (count (filter #(instance? clojure.data.xml.node.Element %) content))))))

(defn- empty-element? [e]
  (println "empty-element" e)
  (or (empty? e) (-> e .toString blank?)))

(defn element? [e]
  (and (instance? clojure.lang.LazySeq e)
       (instance? clojure.data.xml.node.Element (first e))))

(defn remove-empty-elements!
  "Remove empty elements (and child elements) in an xml"
  [head xml-data]
  (let [data (if (seq? xml-data) xml-data (:content xml-data))
        rs (reduce (fn [col e]
              (let [content (:content e)]
                (cond
                  (empty-element? content)
                  col

                  (and (not (element? content)) (not (every? empty-element? content)))
                  (merge col e)

                  (and (element? content) (every? true? (map #(empty-element? (:content %)) content)))
                  col

                  (and (child-element? content))
                  (let [_head (xml/element (:tag e) {})]
                    (merge col (remove-empty-element! _head content)))

                  :else col)))
            []
            data)]
    (assoc head :content rs)))


;; test
(remove-empty-element! xml-data (xml/element (:tag xml-data) {}))
0 голосов
/ 05 декабря 2018

Вот довольно простое решение, использующее clojure.walk/postwalk:

(defn remove-empty-elements [xml-data]
  (clojure.walk/postwalk
   (fn [v]
     (cond
       (and (instance? clojure.data.xml.Element v)
            (every? empty? (:content v)))
       nil ;; nil-out elements with no content
       (instance? clojure.data.xml.Element v)
       (update v :content #(filter some? %)) ;; filter nils from contents
       :else v))
   xml-data))

Это работает путем обхода глубины данных XML в первую очередь, замены элементов без :content на ноль и фильтрации этих нулей из другихelements ':content collection.

Примечание: второе предложение (instance? clojure.data.xml.Element v) в cond может быть опущено, если вы просто генерируете строки, потому что xml/emit-str игнорирует nils в коллекциях :content, т.е.выдает одну и ту же строку в любом случае.

(println (xml/emit-str (remove-empty-elements xml-data)))

Форматированный вывод:

<?xml version="1.0" encoding="UTF-8"?>
<foo>
    <name>John</name>
    <address>1 hacker way</address>
    <college>
        <name>mit</name>
    </college>
</foo>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...