首先解釋一下主題,假設我們手上有一份 XML 文件,是一份紀錄書店銷售紀錄的 XML 文件,內容為各個分店所賣出的書籍和BD。
<?xml version="1.0"?> <?xml-stylesheet type="text/xsl" href="store2wiki.xsl"?> <sale> <store name="Taipei"> <book ISBN="9784757733299"> <title>バカとテストと召喚獣</title> <author>井上堅二</author> </book> <book ISBN="9784757735057"> <title>バカとテストと召喚獣2</title> <author>井上堅二</author> </book> <BD EAN="4935228096671"> <title>Madoka Magica Vol.1"</title> </BD> </store> </sale>
這樣的文件,正常人類是不會想看的,連我自己打起來都很累。那個角括號看起來真的很不習慣。所以啦,為了要把這份文件做成易讀的格式,我決定用 wiki 的條列方式來呈現此文件,利用縮排來呈現 XML 中的樹狀結構。
- Store: Taipei
- Book (IAN=978-4757733299)
- title: バカとテストと召喚獣
- author: 井上堅二
- Book (IAN=978-4757735057)
- title: バカとテストと召喚獣2
- author: 井上堅二
- BD: (IAN=4935228096671)
- title: Madoka Magica Vol.1
這樣看起來是不是清楚多了?
wiki 的種類很多,我採用 Wikipedia 的系統 MediaWiki,用它的語法的話,文件必須寫成如下的形式:
* store: Taipei
:* book (IAN=9784757733299)
::* title: バカとテストと召喚獣
::* author: 井上堅二
:* book: (IAN=9784757735057)
::* title: バカとテストと召喚獣2
::* author: 井上堅二
:* BD: (IAN=4935228096671)
::* title: Madoka Magica Vol.1
每一行前面的星號是標明項目,冒號的數量表示縮排的深度。
問題定義完成。在開始之前,先解釋一些名詞
XML: 全名是 Extensible Markup Language,中文可翻成延伸標記語言。這是一種表示資料的方式。邏輯上來看,是將資料表示成樹狀結構,每個element都會有自己的屬性,並且可以擁有子element。不熟的朋友可參考 http://www.w3schools.com/xml/
XSLT: 全名是 XSL Transformations,可用來將XML文件轉換成另外一個 XML 或 HTML。不過當你了解他的轉換原理後,就會了解到他可以將XML轉成任何格式的文件。這也就是我這次嘗試使用XSLT將XML轉成MediaWiki的理由。
XSLT讓我們定義轉換XML文件的方法,簡單來說我們要提供的是一套規則,說明對於哪些element要施行何種轉換,瀏覽器會依據規則幫我們轉換文件。簡單的說,瀏覽器會從樹根出發,採用一個可以施行的規則,輸出對應的文字。一般說來在規則中會指示瀏覽器移動到子element,並繼續套用規則。
開始動工。我設計規則只有一條: 印出目前的 element 的所有屬性,每個一行,然後繼續處理子element。大約十多行 XSLT 就可以做到。
指定規則是用 template 指令: <xsl:template match="*">
我們用match來指定element的名稱,有符合的就會套用此規則,這裡採用了萬用字元*,所以可以對應到任何element。
接下來用<xsl:value-of select="name()"/>印出此element的名稱。接著是用<xsl:for-each select="@*">找出此element的所有屬性,注意到屬性的開頭是@。
以上三個指令就能讓我們作絕大多數的事,template是用來建立規則,value-of 是用來輸出文字,for-each是用來處理每個子element。
說起來簡單,麻煩的是要在每行的最前面加入冒號,這必須根據目前的深度來決定。
為了解決這個問題,我用了一個小技巧,由於在每個element我們可以使用 ancestor::* 取得他的祖先,這會是一個sequence,因此我使用 for-each 指令,每看到一個祖先element就印出一個冒號。請見第7行。
另外有兩個小地方要注意。
- XML 中的空白字元: 回頭看看我們的 XML 檔案,裡面含有縮排的空白字元,但是我們輸出時不想要這些空白字元(還有換行符號!)所以加入第三行指令 <xsl:strip-space elements="*"/> 以去除這些字元,各位可以把這行拿掉看看會如何 :)
- 將最內層的 element 和 parent 一起顯示: 你會注意到 title 和 author 這兩個 element 內部只含有文字,對於 XSLT 來說,文字也算是 element 的一個子 element,所以依照我們的規則,他會被顯示在下一行。但是我不想要這種結果,所以就採用了11~13行的手法,11行表示只套用內含文字的子element,然後12行印出換行,最後13行再套用剩餘的子element。
總計15行code搞定!
<?xml version="1.0" encoding="ISO-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:strip-space elements="*"/> <xsl:output indent="no" method="text" /> <xsl:template match="text()">:<xsl:value-of select="."/></xsl:template> <xsl:template match="*"> <xsl:for-each select="ancestor::*">:</xsl:for-each>* <xsl:value-of select="name()"/> <xsl:for-each select="@*"> <xsl:text> (</xsl:text><xsl:value-of select="name(.)"/>=<xsl:value-of select="."/><xsl:text>)</xsl:text> </xsl:for-each> <xsl:apply-templates select="text()"/> <xsl:text> </xsl:text> <xsl:apply-templates select="*"/> </xsl:template> </xsl:stylesheet>
參考資料
[1] W3C 的官方文件 http://www.w3.org/TR/xslt
[2] FAQ 專區 http://www.dpawson.co.uk/xsl/sect2/sect21.htm
yaml也是一個human readable的markup language
ReplyDelete特點是跟python一樣用indent做structure layout
你的例子大概長這樣(可能有錯)
---
store: Taipei
book: (IAN=9784757733299)
title: バカとテストと召喚獣
author: 井上堅二
book: (IAN=9784757735057)
title: バカとテストと召喚獣2
author: 井上堅二
BD: (IAN=4935228096671)
title: Madoka Magica Vol.1
reference: http://en.wikipedia.org/wiki/YAML
排版爛掉了= =
ReplyDelete