1

我面前有一个任务,我有一个 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    ")

我无法弄清楚如何处理那棵树以使其成为我想要的形状。assoc我目前的 尝试很脆弱 - 注重cxr功能。CLdestructuring-bind似乎是我想要的,但我不知道如何应用它。我正在尝试将上述结构转换为:

(Object
  (id "100520")
  (type_id "77")
  (has_extras "")
  (author_id "7")
  (summary "To Sir Duke, with love"))
  • destructuring-bind实际上是我需要的工具吗?
  • 如果是这样,我应该如何应用它来从我的数据的一种形状到另一种形状?
  • 如果没有,什么正确的工具?
4

1 回答 1

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标签的规则返回一个单元素列表,并使用mapcanfromcl包将这些单元素列表连接在一起。垃圾元素nil和空格字符串正好匹配_规则,所以它们被转换为空列表并从结果中消失。

我将转换器编写为递归函数,但为了稳健性,您可以轻松地将其拆分为仅与 sexps 匹配的顶级转换(Object ...)器,以及仅与 sexps 匹配的单独转换(field ... )器。

于 2012-10-31T23:21:04.103 回答