如何在clojure中优雅地解析xml

时间:2018-04-07 02:28:48

标签: clojure

我有这段代码从XML构建句子如下所示。我想知道什么是替代代码,在被黑客攻击后会更具可读性。

     (mapcat
        (fn [el]
           (map special-join
              (map
                  (fn [el] (map zip-xml/text (zip-xml/xml-> el :word)))
                  (zip-xml/xml-> el :sentence))))
        (zip-xml/xml-> root :document))

上面的代码不是很易读,因为重复的内联函数定义与嵌套探测相结合,但是将它们拆分成独立的函数,就像在this official tutorial中一样,对于我这样简单而言对我来说真的没有意义例。

为了完整性,这里是解析

的重复XML结构
<document>
  <sentence id="1">
    <word id="1.1">Foo</w>
    <word id="1.2">bar</w>
  </sentence>
</document>

2 个答案:

答案 0 :(得分:3)

拉链在这种情况下可能有点过分。 clojure.xml/parse将为您提供一个表示HTML的简单数据结构。

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

(def doc
  (->
"<document>
  <sentence id=\"1\">
    <word id=\"1.1\">
      Foo
    </word>
    <word id=\"1.2\">
      bar
    </word>
  </sentence>
</document>
" .getBytes java.io.ByteArrayInputStream. xml/parse))

然后,您可以使用xml-seq获取所有<sentence>代码及其子代,收集孩子的文字内容,修剪空白以及加入空格。

(->> doc
  xml-seq
  (filter (comp #{:sentence} :tag))
  (map :content)
  (map #(transduce
          (comp
            (mapcat :content)
            (map string/trim)
            (interpose " "))
          str %)))

答案 1 :(得分:0)

我不喜欢拉链在Clojure中的工作方式,我没有看过clojure.zip/xml-zipclojure.data.zip/xml->(令人困惑的是它们是两个独立的库!)。

相反,我可以建议你试试tupelo.forest图书馆吗?这是an overview from the 2017 Clojure/Conj

以下是使用tupelo.forest的实时解决方案。我添加了第二句话使其更有趣:

(dotest
  (with-forest (new-forest)
    (let [xml-str        (ts/quotes->double
                           "<document>
                              <sentence id='1'>
                                <word id='1.1'>foo</word>
                                <word id='1.2'>bar</word>
                              </sentence>
                              <sentence id='2'>
                                <word id='2.1'>beyond</word>
                                <word id='2.2'>all</word>
                                <word id='2.3'>recognition</word>
                              </sentence>
                            </document>")

          root-hid       (add-tree-xml xml-str)
          >>             (remove-whitespace-leaves)
          bush-no-blanks (hid->bush root-hid)
          sentence-hids  (find-hids root-hid [:document :sentence])
          sentences      (forv [sentence-hid sentence-hids]
                           (let [word-hids     (hid->kids sentence-hid)
                                 words         (mapv #(grab :value (hid->leaf %)) word-hids)
                                 sentence-text (str/join \space words)]
                             sentence-text))
          ]
      (is= bush-no-blanks
        [{:tag :document}
         [{:id "1", :tag :sentence}
          [{:id "1.1", :tag :word, :value "foo"}]
          [{:id "1.2", :tag :word, :value "bar"}]]
         [{:id "2", :tag :sentence}
          [{:id "2.1", :tag :word, :value "beyond"}]
          [{:id "2.2", :tag :word, :value "all"}]
          [{:id "2.3", :tag :word, :value "recognition"}]]])
      (is= sentences
        ["foo bar"
         "beyond all recognition"]))))

想法是为每个句子找到hid(十六进制ID,如指针)。在forv循环中,我们找到每个句子的子节点,提取:value,并将其联合成一个字符串。单元测试显示从XML(删除空白节点后)和最终结果解析的树结构。请注意,我们忽略id字段并仅使用树结构来理解句子。

tupelo.forest的文档仍在进行中,但您可以see many live examples here

图珀洛项目lives on GitHub。\

更新

我一直在考虑流数据问题,并添加了一个新函数proc-tree-enlive-lazy来启用大型数据集的延迟处理。这是一个例子:

  (let [xml-str (ts/quotes->double
                  "<document>
                     <sentence id='1'>
                       <word id='1.1'>foo</word>
                       <word id='1.2'>bar</word>
                     </sentence>
                     <sentence id='2'>
                       <word id='2.1'>beyond</word>
                       <word id='2.2'>all</word>
                       <word id='2.3'>recognition</word>
                     </sentence>
                   </document>")
    (let [enlive-tree-lazy     (clojure.data.xml/parse (StringReader. xml-str))
          doc-sentence-handler (fn [root-hid]
                                 (remove-whitespace-leaves)
                                 (let [sentence-hid  (only (find-hids root-hid [:document :sentence]))
                                       word-hids     (hid->kids sentence-hid)
                                       words         (mapv #(grab :value (hid->leaf %)) word-hids)
                                       sentence-text (str/join \space words)]
                                   sentence-text))
          result-sentences     (proc-tree-enlive-lazy enlive-tree-lazy
                                 [:document :sentence] doc-sentence-handler)]
      (is= result-sentences ["foo bar" "beyond all recognition"])) ))

这个想法是你处理连续的子树,在这种情况下,只要你得到[:document :sentence]的子树路径。您传入处理程序函数,该函数将接收root-hid的{​​{1}}。然后将处理程序的返回值放在返回给调用者的输出延迟序列上。