XML 處理
XML 作為一種資料交換和資訊傳遞的格式已經十分普及。而隨著 Web 服務日益廣泛的應用,現在 XML 在日常的開發工作中也扮演了愈發重要的角色。這一小節, 我們將就 Go 語言標準套件中的 XML 相關處理的套件進行介紹。
這個小節不會涉及 XML 規範相關的內容(如需了解相關知識請參考其他文獻),而是介紹如何用 Go 語言來編解碼 XML 檔案相關的知識。
假如你是一名運維人員,你為你所管理的所有伺服器生成了如下內容的 xml 的配置檔案:
上面的 XML 文件描述了兩個伺服器的資訊,包含了伺服器名和伺服器的 IP 資訊,接下來的 Go 例子以此 XML 描述的資訊進行操作。
解析 XML
如何解析如上這個 XML 檔案呢? 我們可以透過 xml 套件的 Unmarshal
函式來達到我們的目的
data 接收的是 XML 資料流,v 是需要輸出的結構,定義為 interface,也就是可以把 XML 轉換為任意的格式。我們這裡主要介紹 struct 的轉換,因為 struct 和 XML 都有類似樹結構的特徵。
範例程式碼如下:
XML 本質上是一種樹狀結構,而我們可以定義與之匹配的 go 語言的 struct 型別,然後透過 xml.Unmarshal 來將 xml 中的資料解析成對應的 struct 物件。如上例子輸出如下資料
上面的例子中,將 xml 檔案解析成對應的 struct 物件是透過xml.Unmarshal
來完成的,這個過程是如何實現的?可以看到我們的 struct 定義後面多了一些類似於xml:"serverName"
這樣的內容,這個是 struct 的一個特性,它們被稱為 struct tag,它們是用來輔助反射的。我們來看一下 Unmarshal
的定義:
我們看到函式定義了兩個參數,第一個是 XML 資料流,第二個是儲存的對應型別,目前支援 struct、slice 和 string,XML 套件內部採用了反射來進行資料的對映,所以 v 裡面的欄位必須是匯出的。Unmarshal
解析的時候 XML 元素和欄位怎麼對應起來的呢?這是有一個優先順序讀取流程的,首先會讀取 struct tag,如果沒有,那麼就會對應欄位名。必須注意一點的是解析的時候 tag、欄位名、XML 元素都是區分大小寫的的,所以必須一一對應欄位。
Go 語言的反射機制,可以利用這些 tag 資訊來將來自 XML 檔案中的資料反射成對應的 struct 物件,關於反射如何利用 struct tag 的更多內容請參閱 reflect 中的相關內容。
解析 XML 到 struct 的時候遵循如下的規則:
如果 struct 的一個欄位是 string 或者 []byte 型別且它的 tag 含有
",innerxml"
,Unmarshal 將會將此欄位所對應的元素內所有內嵌的原始 xml 累加到此欄位上,如上面例子 Description 定義。最後的輸出是
如果 struct 中有一個叫做 XMLName,且型別為 xml.Name 欄位,那麼在解析的時候就會儲存這個 element 的名字到該欄位,如上面例子中的 servers。
如果某個 struct 欄位的 tag 定義中含有 XML 結構中 element 的名稱,那麼解析的時候就會把相應的 element 值賦值給該欄位,如上 servername 和 serverip 定義。
如果某個 struct 欄位的 tag 定義了中含有
",attr"
,那麼解析的時候就會將該結構所對應的 element 的與欄位同名的屬性的值賦值給該欄位,如上 version 定義。如果某個 struct 欄位的 tag 定義 型如
"a>b>c"
,則解析的時候,會將 xml 結構 a 下面的 b 下面的 c 元素的值賦值給該欄位。如果某個 struct 欄位的 tag 定義了
"-"
,那麼不會為該欄位解析匹配任何 xml 資料。如果 struct 欄位後面的 tag 定義了
",any"
,如果他的子元素在不滿足其他的規則的時候就會匹配到這個欄位。如果某個 XML 元素包含一條或者多條註釋,那麼這些註釋將被累加到第一個 tag 含有",comments"的欄位上,這個欄位的型別可能是 []byte 或 string,如果沒有這樣的欄位存在,那麼註釋將會被拋棄。
上面詳細講述了如何定義 struct 的 tag。 只要設定對了 tag,那麼 XML 解析就如上面範例般簡單,tag 和 XML 的 element 是一一對應的關係,如上所示,我們還可以透過 slice 來表示多個同級元素。
注意: 為了正確解析,go 語言的 xml 套件要求 struct 定義中的所有欄位必須是可匯出的(即首字母大寫)
輸出 XML
假若我們不是要解析如上所示的 XML 檔案,而是產生它,那麼在 go 語言中又該如何實現呢? xml 套件中提供了 Marshal
和MarshalIndent
兩個函式,來滿足我們的需求。這兩個函式主要的區別是第二個函式會增加字首和縮排,函式的定義如下所示:
兩個函式第一個參數是用來產生 XML 的結構定義型別資料,都是回傳產生的 XML 資料流。
下面我們來看一下如何輸出如上的 XML:
上面的程式碼輸出如下資訊:
和我們之前定義的檔案的格式一模一樣,之所以會有os.Stdout.Write([]byte(xml.Header))
這句程式碼的出現,是因為xml.MarshalIndent
或者xml.Marshal
輸出的資訊都是不帶 XML 頭的,為了產生正確的 xml 檔案,我們使用了 xml 套件預定義的 Header 變數。
我們看到 Marshal
函式接收的參數 v 是 interface{}型別的,即它可以接受任意型別的參數,那麼 xml 套件,根據什麼規則來產生相應的 XML 檔案呢?
如果 v 是 array 或者 slice,那麼輸出每一個元素,類似value
如果 v 是指標,那麼會 Marshal 指標指向的內容,如果指標為空,什麼都不輸出
如果 v 是 interface,那麼就處理 interface 所包含的資料
如果 v 是其他資料型別,就會輸出這個資料型別所擁有的欄位資訊
產生的 XML 檔案中的 element 的名字又是根據什麼決定的呢?元素名按照如下優先順序從 struct 中取得:
如果 v 是 struct,XMLName 的 tag 中定義的名稱
型別為 xml.Name 的名叫 XMLName 的欄位的值
透過 struct 中欄位的 tag 來取得
透過 struct 的欄位名用來取得
marshall 的型別名稱
我們應如何設定 struct 中欄位的 tag 資訊以控制最終 xml 檔案的產生呢?
XMLName 不會被輸出
tag 中含有
"-"
的欄位不會輸出tag 中含有
"name,attr"
,會以 name 作為屬性名,欄位值作為值輸出為這個 XML 元素的屬性,如上 version 欄位所描述tag 中含有
",attr"
,會以這個 struct 的欄位名作為屬性名輸出為 XML 元素的屬性,類似上一條,只是這個 name 預設是欄位名了。tag 中含有
",chardata"
,輸出為 xml 的 character data 而非 element。tag 中含有
",innerxml"
,將會被原樣輸出,而不會進行常規的編碼過程tag 中含有
",comment"
,將被當作 xml 註釋來輸出,而不會進行常規的編碼過程,欄位值中不能含有"--"字串tag 中含有
"omitempty"
,如果該欄位的值為空值那麼該欄位就不會被輸出到 XML,空值包括:false、0、nil 指標或 nil 介面,任何長度為 0 的 array, slice, map 或者 stringtag 中含有
"a>b>c"
,那麼就會迴圈輸出三個元素 a 包含 b,b 包含 c,例如如下程式碼就會輸出
上面我們介紹了如何使用 Go 語言的 xml 套件來編/解碼 XML 檔案,重要的一點是對 XML 的所有操作都是透過 struct tag 來實現的,所以學會對 struct tag 的運用變得非常重要,在文章中我們簡要的列舉了如何定義 tag。更多內容或 tag 定義請參看相應的官方資料。
links
Last updated