2012-10-31 30 views
1

我有一個任務在我面前,我有一個XML文檔,我需要 以系統化的方式將其轉換爲另一個XML文檔 - 將標籤Foo 更改爲標籤欄,將所有Qux標籤更改爲Frob的name="frob"屬性標籤, 等。我不知道如何使用XSLT,但我對自己說 - 嘿,如果我必須對基於樹的數據進行一系列轉換, 聽起來像Lisp擅長的東西!如何使用destructuring-bind將XML轉換爲特定的s表達式樹?

所以我有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函數上很重。 CL的destructuring-bind似乎是我想要的,但我 無法弄清楚如何應用它。我想上述結構 變成這樣:

(Object 
    (id "100520") 
    (type_id "77") 
    (has_extras "") 
    (author_id "7") 
    (summary "To Sir Duke, with love")) 
  • destructuring-bind實際上是工具,我需要什麼?
  • 如果是這樣,我該如何應用它來從一種形式的數據到另一種形式?
  • 如果不是,這個合適的工具嗎?
+0

如果XSLT是一個選項,我會爲此任務認真推薦它。你可以用一個非常簡潔的轉換來實現你所描述的,然後去喝啤酒。爲了更直接地解決您的問題,我不認爲'destructuring-bind'或emacs lisp是您需要的工具。 – harpo

+0

小提琴。我以前很怕那個。我並不擔心學習XSLT,而是在截止日期之前這樣做。我的想法是「我有我的Lisp工具,讓我們看看我能做些什麼。」 –

+0

爲了跟進,我對emacs lisp沒有任何反應。我只是不認爲它是用於XML轉換的正確工具。出於這個原因,我只寫了一些代碼將'org'文檔的內部表示導出到XML中,以便我可以對它們執行轉換(其目標輸出是XHTML)。這是我的首選,但在你的情況下,除了你正在做的實際轉換之外,還需要雙向翻譯*,所以你可能會迷失在翻譯中,可以這麼說。 – harpo

回答

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))` 

的S-EXP的field標籤與名稱,但(在本例類似has_extras)沒有任何內容相匹配。等等。對於不符合這些規則的任何內容,最後一條規則_返回nil。每條規則的右邊可以是任何Lisp表達式。對於這種轉換,使用backquote和unquote是非常有用的,這有利於模板看起來就像它們匹配的規則。

唯一有點棘手的部分是如何積累(Object ...)子節點的轉換值。如果我們使用mapcar來遍歷它們,我們最終會得到不需要的nil s,其中最初有空白字符串和其他垃圾。解決方案是讓field標籤的規則返回一個元素列表,並使用cl包中的mapcan將這些單元素列表連接在一起。像nil這樣的垃圾元素和空白字符串只與_規則匹配,所以它們被轉換爲空列表並從結果中消失。

我寫了變壓器作爲遞歸函數,但爲了魯棒性,您可以輕鬆地將其分解爲只與(Object ...)六分體相匹配的頂級變壓器以及僅與(field ...)四分相匹配的單獨變壓器。

+0

我不知道pcase是否存在!我很高興能嘗試你的建議,謝謝。 –

+0

希望它有幫助!我認爲,'pcase'是相當新的。我自己並沒有使用過它,但對於這種事情似乎非常有用。我不知道是否有快速的方法來使用Elisp從轉換後的s-exps生成XML,但是..? –

+0

我打算用基本的字符串操作來完成s-exprs-to-XML部分,但我可能會嘗試[xml-to-string.el](https://github.com/upgradingdave/xml-to-string )如果我覺得活潑。至於pcase:是的,它只是emacs 24,而且它表明文檔以非常人性化的方式簡潔。我應該寫關於它的知識 - 並瞭解它。 –