Golang解析具有多命名空间的嵌套XML

Golang解析具有多命名空间的嵌套XML 我想从一个嵌套的XML中解析出一部分值。我希望不需要引入任何依赖,只使用标准库的xml包。

完整的示例是嵌套的,并且涉及几个不同的命名空间。我看到有很多关于XML命名空间的未解决问题,所以我创建了一个最小的可工作示例,其中包含我认为具有命名空间的最小细节级别,并且成功提取了我想要的内容。

小示例 - Go Playground

...
// 已缩写,完整数据结构请查看Go Playground链接

	reader := strings.NewReader(dat1)
	decoder := xml.NewDecoder(reader)

	//https://stackoverflow.com/questions/59615418/how-to-parse-xml-in-slice-format
	type StuffList struct {
		Stuff []string `xml:"SubLevel1>SubLevel2"`
	}

	var sl StuffList

	err := decoder.Decode(&sl)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("rl:", sl.Stuff)

有了这个基础,我尝试将其应用到完整的文档上,该文档有额外的嵌套层级和命名空间,但它返回了一个空切片。

完整示例 - Go Playground 链接

  1. 我是否真的可以用 Decode 来读取这个?不确定 Decode 和需要调用 Unmarshal 之间的界限在哪里?我是否需要在结构体标签中添加更多层级的细节才能正确“看到”树?我尝试添加一个或两个额外的层级,但没有成功。

  2. 什么时候需要结构体的结构体,而不是直接读取到切片?我以为在标签中指定正确的路径基本上可以让我免于为整个树创建结构体?

  3. 有没有办法内省地查看较小的可工作示例在哪里有效,而较大的示例在哪里失败?我不太理解两者之间发生了什么。我尝试了一下 delve 包,但没能得到任何有用的信息。


更多关于Golang解析具有多命名空间的嵌套XML的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

在你将 SubLevel1SubLevel2 嵌入到额外的层级后,Stuff 结构体中的路径必须变得更长才能“深入”到那个层级。我进行了如下更改:

type StuffList struct {
		Stuff []string `xml:"SubLevel1>SubLevel2"`
	}

改为

type StuffList struct {
		Stuff []string `xml:"Response2>TopLevel>SubLevel1>SubLevel2"`
	}

然后它就正常工作了:Go Playground - The Go Programming Language

所以,回答你的问题:

  1. 是的 :smiley:

  2. 当你需要从 XML 的更多层级获取信息时,我建议添加结构体层级和/或结构体切片。如果你只关心来自特定 XPath 的信息,那么你目前的做法就很好。如果你不仅需要多个 XPath,还需要知道哪个 SubLevel2 对应哪个 SubLevel1,那么你可能需要定义一个 SubLevel1 结构体切片,其中每个结构体都包含一个用于存储 SubLevel2 值的字符串切片。

  3. 我不确定是否有办法做到这一点。我只是滚动浏览了一下,注意到你第二个例子中的层级嵌套得更深,但路径却相同,于是我开始添加前缀,先是 "TopLevel",然后是 "Response2",直到它返回值。我不确定是否有调试的方法。

更多关于Golang解析具有多命名空间的嵌套XML的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Golang中解析具有多命名空间的嵌套XML时,标准库的xml包完全能够处理。根据你的完整示例,问题主要在于命名空间的处理和结构体标签的路径定义。

以下是针对你问题的解决方案:

package main

import (
	"encoding/xml"
	"fmt"
	"strings"
)

const data = `<?xml version="1.0" encoding="UTF-8"?>
<Root xmlns="http://example.com/root" xmlns:ns1="http://example.com/ns1">
    <ns1:Level1>
        <ns1:Level2>
            <ns1:Level3>
                <ns1:Value>First</ns1:Value>
                <ns1:Value>Second</ns1:Value>
                <ns1:Value>Third</ns1:Value>
            </ns1:Level3>
        </ns1:Level2>
    </ns1:Level1>
</Root>`

func main() {
	// 定义匹配命名空间的结构体
	type Value struct {
		XMLName xml.Name
		Content string `xml:",chardata"`
	}

	type Level3 struct {
		XMLName xml.Name `xml:"http://example.com/ns1 Level3"`
		Values  []Value  `xml:"http://example.com/ns1 Value"`
	}

	type Level2 struct {
		XMLName xml.Name `xml:"http://example.com/ns1 Level2"`
		Level3  Level3   `xml:"http://example.com/ns1 Level3"`
	}

	type Level1 struct {
		XMLName xml.Name `xml:"http://example.com/ns1 Level1"`
		Level2  Level2   `xml:"http://example.com/ns1 Level2"`
	}

	type Root struct {
		XMLName xml.Name `xml:"http://example.com/root Root"`
		Level1  Level1   `xml:"http://example.com/ns1 Level1"`
	}

	// 解析XML
	var root Root
	err := xml.Unmarshal([]byte(data), &root)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// 提取值
	var result []string
	for _, v := range root.Level1.Level2.Level3.Values {
		result = append(result, v.Content)
	}
	
	fmt.Printf("Extracted values: %v\n", result)
}

对于你的具体问题:

  1. Decode vs Unmarshal:两者都可以使用。Decode用于流式解析,Unmarshal用于解析完整字节切片。在结构体标签中需要包含完整的命名空间路径。

  2. 结构体嵌套:当XML具有复杂嵌套结构时,需要创建对应的嵌套结构体。虽然可以使用路径表达式,但对于多命名空间的复杂XML,显式结构体定义更可靠。

  3. 调试方法:可以使用以下代码查看解析过程:

func debugUnmarshal(data []byte) {
	decoder := xml.NewDecoder(strings.NewReader(data))
	for {
		token, err := decoder.Token()
		if err != nil {
			break
		}
		switch t := token.(type) {
		case xml.StartElement:
			fmt.Printf("Start: %s (Namespace: %s)\n", t.Name.Local, t.Name.Space)
		case xml.EndElement:
			fmt.Printf("End: %s\n", t.Name.Local)
		case xml.CharData:
			if strings.TrimSpace(string(t)) != "" {
				fmt.Printf("Content: %s\n", string(t))
			}
		}
	}
}

对于你的完整示例,关键是要在结构体标签中正确指定命名空间。以下是一个更简洁的解决方案:

type Item struct {
	XMLName xml.Name `xml:"http://example.com/ns1 Item"`
	Value   string   `xml:"http://example.com/ns1 Value"`
}

type ItemList struct {
	XMLName xml.Name `xml:"http://example.com/ns1 ItemList"`
	Items   []Item   `xml:"http://example.com/ns1 Item"`
}

type Response struct {
	XMLName   xml.Name `xml:"http://example.com/root Response"`
	ItemLists []ItemList `xml:"http://example.com/ns1 ItemList"`
}

func parseComplexXML(xmlData string) {
	var response Response
	err := xml.Unmarshal([]byte(xmlData), &response)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	
	for _, list := range response.ItemLists {
		for _, item := range list.Items {
			fmt.Printf("Item value: %s\n", item.Value)
		}
	}
}

这种方法确保正确识别每个元素的命名空间,从而准确解析嵌套的XML结构。

回到顶部