Golang遇到的问题是从文件中遍历和填充XML。

huangapple go评论76阅读模式
英文:

golang trouble traversing, populating xml from file

问题

我对使用golang相对较新,我需要一些关于从XML文件中填充结构树的建议。我已经修复了XML结构与结构定义之间的不一致性。更新了playground链接;完整的带有示例XML的代码位于http://play.golang.org/p/1ymyESO2jp。

XML文件中包含属性和值(chardata)的组合:

<?xml version="1.0" encoding="UTF-8"?>
<system name="SystemA123" enabled="true" open="9" close="15" timeZone="America/Denver" freq="4h" dailyMax="1" override="false">
	<hosts>
		<host address="10.1.2.3">
			<command>"free -mo</command>
			<command>"cat /proc/cpuinfo | grep processor"</command>
			<command>"ifconfig eth0 down"</command>
			<command>"shutdown -r now"</command>
			<command>"cat /proc/loadavg"</command>
		</host>
		<host address="10.1.2.4">
			...更多命令command元素
		</host>
	</hosts>
</system>

我已经构建了相应的结构体:

type SystemConfig struct {
	XMLName   xml.Name `xml:"system"`
	SysName   string   `xml:"name,attr"`
	Enabled   bool     `xml:"enabled,attr"`
	OpenHour  int      `xml:"open,attr"`
	CloseHour int      `xml:"close,attr"`
	TimeZone  string   `xml:"timeZone,attr"`
	Frequency string   `xml:"freq,attr"` //将使用time.ParseDuration来读取这里指定的时间间隔
	DailyMax  int      `xml:"dailyMax,attr"`
	Override  bool     `xml:"override,attr"`
	Hosts     []*Hosts `xml:"hosts"`
}

type Hosts struct {
	XMLName xml.Name `xml:"hosts"`
	Host    []*Host  `xml:"host"`
}

type Host struct {
	XMLName  xml.Name        `xml:"host"`
	IPaddr   string          `xml:"address,attr"`
	Commands []*HostCommands `xml:"command"`
}

type HostCommands struct {
	XMLName xml.Name `xml:"command"`
	Command string   `xml:",chardata"`
}

我正在使用以下代码将XML文件读入结构体:

var sc *SystemConfig
xmlConf, err := os.Open(appConfFile)
defer xmlConf.Close()
sc, err = ReadSystemConfig(xmlConf)

其中ReadSystemConfig方法定义如下:

func ReadSystemConfig(reader io.Reader) (*SystemConfig, error) {
	sysConf := &SystemConfig{}
	decoder := xml.NewDecoder(reader)
	if err := decoder.Decode(sysConf); err != nil {
		//fmt.Println("error decoding sysConf in ReadSystemConfig: %v", err)
		return nil, err
	}
	return sysConf, nil
}

当我按原样运行代码时,包括一些fmt.Printf语句来验证数据是否正确加载,SystemConfig的属性被正确加载,但无法加载任何Hosts数据。当我询问fmt.Printf("first IP address: %v\n", sc.Hosts[0].Host[0].IPaddr)时,会抛出索引超出范围的恐慌错误。

我觉得我已经正确设置了结构体(如果没有,请提供建议);我尝试删除<system>标签并修改代码以从<hosts>标签开始,但是以这种方式我无法获取任何数据(相同的索引超出范围错误)。所以,我相信我在这里弄乱了2-3行代码,但是我找不到一个从文件中读取相对简单的多级XML的现实示例。

最后,我愿意接受关于在JSON中进行此配置的建议。最初我没有选择它,因为我认为更深层次的嵌套会更难阅读;此外,我在其他项目中使用的许多内容都是基于XML的,所以我想在这里学到一些东西,以便将其应用于那些其他(目前是基于Java的)项目。

如常,提前感谢您愿意提供的任何建议或帮助。

英文:

I'm relatively new to working with golang and I could use some advice on populating a struct tree from an XML file. edit: I fixed an inconsistency with the XML structure vs the struct definitions. Updated the playground link; the full code with a sample XML is at http://play.golang.org/p/1ymyESO2jp .

The XML file has a combination of attributes and values (chardata)

&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;system name=&quot;SystemA123&quot; enabled=&quot;true&quot; open=&quot;9&quot; close=&quot;15&quot; timeZone=&quot;America/Denver&quot; freq=&quot;4h&quot; dailyMax=&quot;1&quot; override=&quot;false&quot;&gt;
	&lt;hosts&gt;
		&lt;host address=&quot;10.1.2.3&quot;&gt;
			&lt;command&gt;&quot;free -mo&lt;/command&gt;
			&lt;command&gt;&quot;cat /proc/cpuinfo | grep processor&quot;&lt;/command&gt;
			&lt;command&gt;&quot;ifconfig eth0 down&quot;&lt;/command&gt;
			&lt;command&gt;&quot;shutdown -r now&quot;&lt;/command&gt;
			&lt;command&gt;&quot;cat /proc/loadavg&quot;&lt;/command&gt;
		&lt;/host&gt;
        &lt;host address=&quot;10.1.2.4&quot;&gt;
                  ... more commands&gt;command elements
        &lt;/host&gt;

I've build the corresponding structs like so:

type SystemConfig struct {
	XMLName   xml.Name `xml:&quot;system&quot;`
	SysName   string   `xml:&quot;name,attr&quot;`
	Enabled   bool     `xml:&quot;enabled,attr&quot;`
	OpenHour  int      `xml:&quot;open,attr&quot;`
	CloseHour int      `xml:&quot;close,attr&quot;`
	TimeZone  string   `xml:&quot;timeZone,attr&quot;`
	Frequency string   `xml:&quot;freq,attr&quot;` //will use time.ParseDuration to read the interval specified here
	DailyMax  int      `xml:&quot;dailyMax,attr&quot;`
	Override  bool     `xml:&quot;override,attr&quot;`
	Hosts     []*Hosts `xml:&quot;hosts&quot;`
}

type Hosts struct {
	XMLName xml.Name `xml:&quot;hosts&quot;`
	Host    []*Host  `xml:host&quot;`
}

type Host struct {
	XMLName  xml.Name        `xml:&quot;host&quot;`
	IPaddr   string          `xml:&quot;address,attr&quot;`
	Commands []*HostCommands `xml:&quot;command&quot;`
}

type HostCommands struct {
	XMLName xml.Name `xml:&quot;command&quot;`
	Command string   `xml:&quot;,chardata&quot;`
}

and I'm reading the XML file into the structs with this code:

var sc *SystemConfig
xmlConf, err := os.Open(appConfFile)
defer xmlConf.Close()
sc, err = ReadSystemConfig(xmlConf)

with this method definition for ReadSystemConfig

func ReadSystemConfig(reader io.Reader) (*SystemConfig, error) {
	sysConf := &amp;SystemConfig{}
	decoder := xml.NewDecoder(reader)
	if err := decoder.Decode(sysConf); err != nil {
		//fmt.Println(&quot;error decoding sysConf in ReadSystemConfig: %v&quot;, err)
		return nil, err
	}
	return sysConf, nil
}

When I run this as-is, including some fmt.Printf statements to verify the data got loaded properly, the SystemConfig attributes are loaded correctly, but I can't get any of the Hosts data loaded. Where I've asked for fmt.Printf(&quot;first IP address: %v\n&quot;, sc.Hosts[0].Host[0].IPaddr, the index out of range panic is thrown.

Hi:app pd$ ./app -f ../config/conf.xml
SystemConfig:SysName SystemA123
SystemConfig:Enabled true
SystemConfig:OpenHour 9
SystemConfig:CloseHour 15
SystemConfig:TimeZone America/Denver
SystemConfig:Frequency 4h
SystemConfig:DailyMax 1
SystemConfig:Override false
panic: runtime error: index out of range

goroutine 1 [running]:
runtime.panic(0xd9cc0, 0x1fecf7)
	/usr/local/go/src/pkg/runtime/panic.c:266 +0xb6
main.main()
	/Users/pd/golang/src/local/boxofsand/app/boxofsand.go:95 +0x8ef

I feel like I have the structs setup properly (happy to take advice if I don't); I have tried removing the <system> tags and modifying the code to start from the <hosts> tags, but I wasn't able to pull any data that way, either (same index out of range error). So, I'm confident that I have borked up 2-3 lines of code here, but I can't find a realistic example online that reads relatively simple, multi-level XML from a file.

Lastly, I'd be open to advice on doing this configuration in JSON instead. I didn't choose that initially because I thought the deeper nesting would be harder to read; also, a lot of what I work with on other projects is XML-based, so I'd like to learn something here that I can apply to those other (for now, Java-based) projects.

As always, thanks in advance for any advice or help you're willing to lend.

edit: I saw a consistency issue in the XML skeleton vs the struct definitions. I modified the XML to suit the structs by removing the <commands> tagset

答案1

得分: 3

Patrick,看起来你的结构体存在一些不一致性(当我刚开始尝试使用Go解析嵌套的xml/json时,我也遇到了同样的问题)。主要是你有一些数组的数组的Host对象。我调整了你的结构体,现在它在我的环境中可以成功解析:

type SystemConfig struct {
    XMLName   xml.Name `xml:"system"`
    SysName   string   `xml:"name,attr"`
    Enabled   bool     `xml:"enabled,attr"`
    OpenHour  int      `xml:"open,attr"`
    CloseHour int      `xml:"close,attr"`
    TimeZone  string   `xml:"timeZone,attr"`
    Frequency string   `xml:"freq,attr"` //将使用time.ParseDuration来读取这里指定的时间间隔
    DailyMax  int      `xml:"dailyMax,attr"`
    Override  bool     `xml:"override,attr"`
    Hosts     []Host   `xml:"hosts>host"`
}

type Host struct {
    XMLName  xml.Name      `xml:"host"`
    IPaddr   string        `xml:"address,attr"`
    Commands []HostCommand `xml:"commands>command"`
}

type HostCommand struct {
    XMLName xml.Name `xml:"command"`
    Command string   `xml:",chardata"`
}

另外,我在解决Go中的编码问题时发现的最有用的一点是,构建你的结构体,向其中添加一些模拟数据,然后输出数据并查看Go的格式化结果。从那里,通常很容易看出你出了哪些问题。

例如,这是我用来迭代并找出你的结构体有什么问题的代码:

package main

import (
    "encoding/xml"
    "fmt"
)

var (
    xmlData = `...你的xml片段...`
)

// ...结构体...

func main() {
    conf, _ := ReadSystemConfig(xmlData)
    fmt.Printf("%#v\n", conf)
    data, _ := WriteSystemConfig(conf)
    fmt.Printf("%#v\n", data)
}

func ReadSystemConfig(data string) (*SystemConfig, error) {
    sysConf := &SystemConfig{}
    if err := xml.Unmarshal([]byte(data), sysConf); err != nil {
        return nil, err
    }
    return sysConf, nil
}

func WriteSystemConfig(sysConf *SystemConfig) (string, error) {
    dataBytes, err := xml.Marshal(sysConf)
    if err != nil {
        return "", err
    }
    return string(dataBytes), nil
}

通过这样做,我能够读取你的xml并将其输出,以查看Go能够解析的内容,然后我猜测了几次(之前在Go中进行过xml解析),并进行迭代,直到所有数据都正确输出。

希望对你有所帮助!

英文:

Patrick, it looks like your structs had a few inconsistencies (I ran into the same issues when I first started trying to use Go to parse nested xml/json). Mostly just that you had arrays of arrays of Host objects. I tweaked your structs and this seems to be parsing well for me:

type SystemConfig struct {
	XMLName   xml.Name `xml:&quot;system&quot;`
	SysName   string   `xml:&quot;name,attr&quot;`
	Enabled   bool     `xml:&quot;enabled,attr&quot;`
	OpenHour  int      `xml:&quot;open,attr&quot;`
	CloseHour int      `xml:&quot;close,attr&quot;`
	TimeZone  string   `xml:&quot;timeZone,attr&quot;`
	Frequency string   `xml:&quot;freq,attr&quot;` //will use time.ParseDuration to read the interval specified here
	DailyMax  int      `xml:&quot;dailyMax,attr&quot;`
	Override  bool     `xml:&quot;override,attr&quot;`
	Hosts     []Host   `xml:&quot;hosts&gt;host&quot;`
}

type Host struct {
	XMLName  xml.Name      `xml:&quot;host&quot;`
	IPaddr   string        `xml:&quot;address,attr&quot;`
	Commands []HostCommand `xml:&quot;commands&gt;command&quot;`
}

type HostCommand struct {
	XMLName xml.Name `xml:&quot;command&quot;`
	Command string   `xml:&quot;,chardata&quot;`
}

As an additional note, one of the most useful things I have found while working through encoding issues in Go, build your structures, throw some mock data into them, then spit out the data and see how Go formats it. From there, it's usually pretty easy to see where you went wrong.

For example, here is the code I used to iterate and figure out what was wrong with your structs:

package main

import (
	&quot;encoding/xml&quot;
	&quot;fmt&quot;
)

var (
	xmlData = `... your xml chunk ...`
)

// ... structs ...

func main() {
	conf, _ := ReadSystemConfig(xmlData)
	fmt.Printf(&quot;%#v\n&quot;, conf)
	data, _ := WriteSystemConfig(conf)
	fmt.Printf(&quot;%#v\n&quot;, data)
}

func ReadSystemConfig(data string) (*SystemConfig, error) {
	sysConf := &amp;SystemConfig{}
	if err := xml.Unmarshal([]byte(data), sysConf); err != nil {
		return nil, err
	}
	return sysConf, nil
}

func WriteSystemConfig(sysConf *SystemConfig) (string, error) {
	dataBytes, err := xml.Marshal(sysConf)
	if err != nil {
		return &quot;&quot;, err
	}
	return string(dataBytes), nil
}

With that, I was able to read in your xml and spit it back out to see what Go was able to parse, then I guessed a few times (having done xml parsing in Go before), and iterated until all the data came back out.

Hope this helps!

答案2

得分: 0

我决定将<Hosts>标签集视为多余的,并从XML结构中删除它们,同时也删除了Hosts结构。当我进行了这个更改后,XML数据成功加载。

我意识到我正在使用一个名为Host的变量,类型为[]*Host,这可能会让运行时混淆。

type SystemConfig struct {
	XMLName   xml.Name `xml:"system"`
	SysName   string   `xml:"name,attr"`
	Enabled   bool     `xml:"enabled,attr"`
	OpenHour  int      `xml:"open,attr"`
	CloseHour int      `xml:"close,attr"`
	TimeZone  string   `xml:"timeZone,attr"`
	Frequency string   `xml:"freq,attr"` //将使用time.ParseDuration来读取这里指定的间隔
	DailyMax  int      `xml:"dailyMax,attr"`
	Override  bool     `xml:"override,attr"`
	Hosts     []*Host  `xml:"host"`
}
type Host struct {
	XMLName  xml.Name        `xml:"host"`
	IPaddr   string          `xml:"address,attr"`
	Commands []*HostCommands `xml:"command"`
}

type HostCommands struct {
	XMLName xml.Name `xml:"command"`
	Command string   `xml:",chardata"`
}
英文:

I decided that the <Hosts> tagset was extraneous and removed them from the XML structure, also removing the Hosts struct. When I made this change, the XML data got loaded properly.

I realized that I was using a variable named Host of type []*Host, which may have been confusing the runtime.

type SystemConfig struct {
	XMLName   xml.Name `xml:&quot;system&quot;`
	SysName   string   `xml:&quot;name,attr&quot;`
	Enabled   bool     `xml:&quot;enabled,attr&quot;`
	OpenHour  int      `xml:&quot;open,attr&quot;`
	CloseHour int      `xml:&quot;close,attr&quot;`
	TimeZone  string   `xml:&quot;timeZone,attr&quot;`
	Frequency string   `xml:&quot;freq,attr&quot;` //will use time.ParseDuration to read the interval specified here
	DailyMax  int      `xml:&quot;dailyMax,attr&quot;`
	Override  bool     `xml:&quot;override,attr&quot;`
	Hosts     []*Host  `xml:&quot;host&quot;`
}
type Host struct {
	XMLName  xml.Name        `xml:&quot;host&quot;`
	IPaddr   string          `xml:&quot;address,attr&quot;`
	Commands []*HostCommands `xml:&quot;command&quot;`
}

type HostCommands struct {
	XMLName xml.Name `xml:&quot;command&quot;`
	Command string   `xml:&quot;,chardata&quot;`
}

huangapple
  • 本文由 发表于 2014年5月17日 00:25:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/23700195.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定