使用Clojure zippers过滤XML格式的节点

时间:2017-11-24 14:59:23

标签: xml clojure

如何使用Clojure拉链过滤XML中的文本节点?例如,您可能有一个漂亮的XML文档,它将元素节点与包含空格的文本节点交错:

(def doc
  "<?xml version=\"1.0\"?>
  <root>
    <a>1</a>
    <b>2</b>
  </root>")

如果要检索root个孩子的内容,可以这样做:

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

(-> doc
    xml/parse-str
    zip/xml-zip
    (zip-xml/xml-> :root zf/children zip-xml/text))

但是,这会返回(" " "1" " " "2" " "),包括空格。

如何过滤拉链,以便只选择元素节点?

我想出了这个。

(def filter-elements (comp (partial filter (comp xml/element? zip/node)) zf/children))

(-> doc
    xml/parse-str
    zip/xml-zip
    (zip-xml/xml-> :root filter-elements zip-xml/text))
; => ("1" "2")

我怀疑它不必要地复杂,因此我正在寻找更好的解决方案。

2 个答案:

答案 0 :(得分:5)

我认为这与一般的XML解析问题有关,即决定哪些空白是有意义的,哪些不是。例如,见Q&amp; A:Why am I getting extra text nodes as child nodes of root node?

我检查过并发现data.xml支持通过选项:skip-whitespace跳过空白。它没有记录(source)。

所以最好在解析阶段解决这个问题。

(-> doc
    (xml/parse-str :skip-whitespace true)
    zip/xml-zip
    (zip-xml/xml-> :root zf/children zip-xml/text))
; => ("1" "2")

答案 1 :(得分:0)

您可以使用the Tupelo library来执行此操作,该{}可以同时使用clojure.data.xmltagsoup解析器进行XML解析:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test)
  (:require
    [tupelo.forest :as tf]
    [tupelo.parse.tagsoup :as tagsoup]
    [tupelo.string :as ts] ))

(dotest
  (let [doc "<?xml version=\"1.0\"?>
             <root>
               <a>1</a>
               <b>2</b>
             </root>"
        result-enlive (tagsoup/parse (ts/string->stream doc))
        result-hiccup (tf/enlive->hiccup result-enlive)
        ]
    (is= result-enlive
      {:tag   :root,
       :attrs {},
       :content
              [{:tag :a, :attrs {}, :content ["1"]}
               {:tag :b, :attrs {}, :content ["2"]}]})

    (is= result-hiccup
      [:root
       [:a "1"]
       [:b "2"]])))