Golang中如何部分解析XML文件
Golang中如何部分解析XML文件 我需要从一个XML文件中提取几个字段,想知道该如何处理。
首先,一些背景信息: 我需要获取一个libvirt虚拟机的快照信息,这些信息是以XML格式编码的。
我通过以下方式实现:
data, _ := snap.GetXMLDesc(0)
(data是一个字符串变量……是的,libvirt将大约8kb的文件内容返回到一个字符串变量中。)
如果我将这个字符串写入一个XML文件,它包含188行(7.8kb)的字段和属性。实际上,我只需要其中的3个字段。
根据网上的各种资料,我了解到要解析这个XML文件,我需要创建一个数据结构,在其中映射所有的字段和属性,等等。当我只需要这个XML文件中的大约3行数据时,肯定有更好的方法吧?
我想到的一个变通方法是把这个字符串变量转储到一个文件中,然后在文件中使用“grep”来获取我需要的信息,但觉得这种方法不够优雅。一定有一种方法可以只映射文件中我需要的信息,而不是全部吧?
以下是XML文件的一个示例。假设我只想要“parent”、“creation type”和“type arch”字段及其属性,除了将XML转储到文件之外,我没有其他思路。
<description>vmman-generated snapshot</description>
<state>shutoff</state>
<parent>
<name>0.clear</name>
</parent>
<creationTime>1667742695</creationTime>
<memory snapshot='no'/>
<disks>
<disk name='vda' snapshot='internal'/>
</disks>
<domain type='kvm'>
<name>alpinedev</name>
<uuid>055e3a07-533a-41ee-ae94-cbee5ff404f0</uuid>
<metadata>
<libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
<libosinfo:os id="http://alpinelinux.org/alpinelinux/3.16"/>
</libosinfo:libosinfo>
</metadata>
<memory unit='KiB'>786432</memory>
<currentMemory unit='KiB'>786432</currentMemory>
<vcpu placement='static'>1</vcpu>
<os>
<type arch='x86_64' machine='pc-q35-7.0'>hvm</type>
<boot dev='hd'/>
</os>
更多关于Golang中如何部分解析XML文件的实战教程也可以访问 https://www.itying.com/category-94-b0.html
这是一个使用你的数据的示例 - Go Playground - The Go 编程语言。
您可以考虑使用XML的“sax”或“流式”解析。
或者也可以使用XPath。
如果您选择的库实现良好,内存消耗将相对较小,加减一些在查找文件时通常会产生、可被垃圾回收的临时对象。
您可以只定义一个结构体,并指定要从数据中读取的标签:Go Playground - The Go Programming Language
比XPath更简单,谢谢。我没想到可以在结构体和XML文档之间进行“部分映射”。我本想在Playground上测试一下,结果被@nobbz的解决方案吸引住了。
谢谢,这个方法有效。
哦……我想我不知怎么地错过了XPath?它和我之前用过的一个Python3库非常相似,那是一个之前(可能是一个移植版/分支版)。这是我今天找到的最接近的解决方案了。
我本来打算采用我在原帖中提到的那种变通方法,整个过程都捏着鼻子进行 
谢谢。我不知道为什么我在搜索时错过了XPath!不过没关系,我现在有解决方案了。
好的,我本以为它可行,但实际上并非如此(提醒自己:编译通过不等于能运行)。我不确定这是因为我对Go的了解尚浅,还是因为我对XML文档的记忆已经生疏了。以下是我需要解析的XML的编辑版本:
<domainsnapshot>
<name>1.hello</name>
<description>vmman-generated snapshot</description>
<state>shutoff</state>
<parent>
<name>0.clear</name>
</parent>
<creationTime>1667742695</creationTime>
</domainsnapshot>
现在,我需要获取快照名称、父级名称(如果存在)以及创建时间。
我构建了以下结构体:
type ParentElement struct {
XMLName xml.Name `xml:"parent"`
Name string `xml:"parentname"`
}
type SnapshotXMLstruct struct {
SnapshotName string `xml:"snapname"`
Creationdate uint64 `xml:"creationdate"`
ParentName ParentElement `xml:"parent",omitempty`
}
我为ParentName设置了omitempty,因为XML文档中可能缺少这个标签。
我用于检索XML并将所需的3个字段追加到我自己的结构体中的代码如下:
var snapXMLdata SnapshotXMLstruct
var snaps []SnapshotXMLstruct
<snip>
snapshots, _ := domain.ListAllSnapshots(0)
for _, snap := range snapshots {
data, _ := snap.GetXMLDesc(0)
err := xml.Unmarshal([]byte(data), &snapXMLdata)
if err != nil {
fmt.Println("Error: ", err)
os.Exit(0)
}
snaps = append(snaps, snapXMLdata)
}
data变量非空,所以问题不在于没有获取到有效的XML文档。
……然而,snapXMLdata是空的,并且err == nil。
我想我还没能理解这个简单的概念 :-/
在Go中部分解析XML文件可以使用xml.Decoder的Token API,这样无需定义完整的结构体。以下是针对你需求的解决方案:
package main
import (
"encoding/xml"
"fmt"
"strings"
)
func main() {
xmlData := `<?xml version="1.0"?>
<snapshot>
<description>vmman-generated snapshot</description>
<state>shutoff</state>
<parent>
<name>0.clear</name>
</parent>
<creationTime>1667742695</creationTime>
<memory snapshot='no'/>
<disks>
<disk name='vda' snapshot='internal'/>
</disks>
<domain type='kvm'>
<name>alpinedev</name>
<uuid>055e3a07-533a-41ee-ae94-cbee5ff404f0</uuid>
<metadata>
<libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
<libosinfo:os id="http://alpinelinux.org/alpinelinux/3.16"/>
</libosinfo:libosinfo>
</metadata>
<memory unit='KiB'>786432</memory>
<currentMemory unit='KiB'>786432</currentMemory>
<vcpu placement='static'>1</vcpu>
<os>
<type arch='x86_64' machine='pc-q35-7.0'>hvm</type>
<boot dev='hd'/>
</os>
</domain>
</snapshot>`
result := parsePartialXML(xmlData)
fmt.Printf("Parent: %s\n", result.Parent)
fmt.Printf("Creation Time: %s\n", result.CreationTime)
fmt.Printf("Architecture: %s\n", result.Arch)
}
type SnapshotInfo struct {
Parent string
CreationTime string
Arch string
}
func parsePartialXML(xmlStr string) SnapshotInfo {
decoder := xml.NewDecoder(strings.NewReader(xmlStr))
var info SnapshotInfo
var inParent, inType bool
for {
token, err := decoder.Token()
if err != nil {
break
}
switch se := token.(type) {
case xml.StartElement:
switch se.Name.Local {
case "parent":
inParent = true
case "creationTime":
var creationTime string
if err := decoder.DecodeElement(&creationTime, &se); err == nil {
info.CreationTime = creationTime
}
case "type":
inType = true
for _, attr := range se.Attr {
if attr.Name.Local == "arch" {
info.Arch = attr.Value
}
}
}
case xml.EndElement:
if se.Name.Local == "parent" {
inParent = false
} else if se.Name.Local == "type" {
inType = false
}
case xml.CharData:
if inParent && string(se) != "\n" && string(se) != " " {
info.Parent = string(se)
}
}
}
return info
}
如果你需要更精确地获取parent的name字段,可以使用这个版本:
func parsePartialXML(xmlStr string) SnapshotInfo {
decoder := xml.NewDecoder(strings.NewReader(xmlStr))
var info SnapshotInfo
var inParent, inParentName bool
for {
token, err := decoder.Token()
if err != nil {
break
}
switch se := token.(type) {
case xml.StartElement:
switch se.Name.Local {
case "parent":
inParent = true
case "name":
if inParent {
inParentName = true
}
case "creationTime":
var creationTime string
if err := decoder.DecodeElement(&creationTime, &se); err == nil {
info.CreationTime = creationTime
}
case "type":
for _, attr := range se.Attr {
if attr.Name.Local == "arch" {
info.Arch = attr.Value
}
}
}
case xml.EndElement:
if se.Name.Local == "parent" {
inParent = false
} else if se.Name.Local == "name" && inParent {
inParentName = false
}
case xml.CharData:
if inParentName {
info.Parent = string(se)
}
}
}
return info
}
对于你的libvirt场景,直接使用返回的字符串:
func GetSnapshotInfo() (SnapshotInfo, error) {
data, err := snap.GetXMLDesc(0)
if err != nil {
return SnapshotInfo{}, err
}
return parsePartialXML(data), nil
}
这种方法只解析你需要的字段,不会处理整个XML结构,内存效率更高。

