如何使用destructuring-bind将XML转换为特定的s表达式树?

时间:2012-10-31 19:55:09

标签: xml emacs elisp

我面前有一个任务,我有一个XML文档,我需要 以系统的方式将其转换为另一个XML文档 - 更改标记Foo 标记Bar,将所有带有name="frob"属性的Qux标记更改为Frob标记, 等等。我对如何使用XSLT一无所知,但我对自己说 - 嘿,如果我必须对基于树的数据进行一系列转换, 这听起来像Lisp擅长的东西!

所以我有一大块XML - 例如:

<Object>
    <field name="id">100520</field>
    <field name="type_id">77</field>
    <field name="has_extras"></field>
    <field name="author_id">7</field>
    <field name="summary">To Sir Duke, with love</field>
</Object>

我用xml-parse tag啜饮了这个并得到:

(Object nil "\n        "
     (field
     ((name . "id"))
     "100520")
    "\n        "
    (field
     ((name . "type_id"))
     "77")
    "\n        "
    (field
     ((name . "has_extras")))
    "\n        "
    (field
     ((name . "author_id"))
     "7")
    "\n        "
    (field
     ((name . "summary"))
     "To Sir Duke, with love")
    "\n    ")

我无法弄清楚如何处理这棵树以使其进入 我想要的形状。我目前的尝试是脆弱的 - assoccxr destructuring-bind功能。 CL的(Object (id "100520") (type_id "77") (has_extras "") (author_id "7") (summary "To Sir Duke, with love")) 似乎是我想要的,但我 无法弄清楚如何应用它。我正试图改变上面的结构 进入这个:

destructuring-bind
  • {{1}}实际上是我需要的工具吗?
  • 如果是这样,我应该如何应用它来从一种形状的数据到另一种形状?
  • 如果没有,那么 正是什么工具?

1 个答案:

答案 0 :(得分:4)

destructuring-bind确实不能胜任这项工作,但在Emacs 24中,您可以使用pcase模式匹配宏非常简洁地完成这项工作,如下所示:

(require 'cl)                ;; for `mapcan'
(require 'pcase)

(defun xslt-in-elisp (xml)
  (pcase xml
    (`(Object . ,rest)
     `(Object . ,(mapcan #'xslt-in-elisp rest)))

    (`(field ((name . ,name)))
     `((,(intern name) "")))

    (`(field ((name . ,name)) ,value)
     `((,(intern name) ,value)))

    (_ nil)))

(xslt-in-elisp
 '(Object nil "\n        "
          (field ((name . "id")) "100520")
          "\n        "
          (field
           ((name . "type_id"))
           "77")
          "\n        "
          (field
           ((name . "has_extras")))
          "\n        "
          (field
           ((name . "author_id"))
           "7")
          "\n        "
          (field
           ((name . "summary"))
           "To Sir Duke, with love")
          "\n    "))

评估为:

(Object
 (id "100520")
 (type_id "77")
 (has_extras "")
 (author_id "7")
 (summary "To Sir Duke, with love"))

工作原理:pcase采用模式匹配的值和一系列子句(PATTERN VALUE)来按顺序尝试。您可以使用M-x describe-function pcase查找详细信息,但基本上模式看起来与您希望它们匹配的模式一样,使用反引用语法指定哪些部分是要绑定的模式匹配变量,哪些部分匹配为文字符号。所以,第一条规则

`(Object . ,rest)

匹配任何以Object作为第一个符号的列表,并将变量rest绑定到任何剩余的元素。规则

`(field ((name . ,name))` 

匹配field标记的S-exp,其中包含名称但没有内容(例如示例中的has_extras)。等等。对于任何与这些规则不匹配的内容,最后一条规则_会返回nil。每个规则的右侧可以是任何Lisp表达式。对于这种转换,使用反引号和取消引用是最有用的,其中模板看起来就像它们匹配的规则一样。

唯一有点棘手的部分是如何累积(Object ...)的子节点的转换值。如果我们使用mapcar迭代它们,我们最终会得到不需要的nil s,其中最初有空格和其他垃圾字符串。解决方案是让field标记的规则返回单元素列表,并使用mapcan包中的cl将这些单元素列表连接在一起。像nil这样的垃圾元素和空白字符串只与_规则匹配,因此它们会转换为空列表并从结果中消失。

我将变换器写成递归函数,但为了实现稳健性,您可以轻松地将其拆分为仅匹配(Object ...)性别的顶级转换器,以及仅与{{{{}}匹配的单独转换器。 1}}性别。