Go语言XML文件的读写操作
来源:云海天教程
时间:2023-01-07 12:12:16 356浏览 收藏
怎么入门Golang编程?需要学习哪些知识点?这是新手们刚接触编程时常见的问题;下面golang学习网就来给大家整理分享一些知识点,希望能够给初学者一些帮助。本篇文章就来介绍《Go语言XML文件的读写操作》,涉及到文件处理,有需要的可以收藏一下
XML(extensible Markup Language) 格式被广泛用作一种数据交换格式,并且自成一种文件格式。与 JSON 相比,XML 复杂得多,手动写起来也啰嗦而且乏味得多。encoding/xml 包可以用在结构体和 XML 格式之间进行编解码,其方式跟 encoding/json 包类似。然而,与 encoding/json 包相比,XML 的编码和解码在功能上更苛刻得多。这部分是由于 encoding/xml 包要求结构体的字段包含格式合理的标签(然而 JSON 格式却不需要)。
同时,Go 1 的 encoding/xml 包没有 xml.Marshaler 接口,因此与编解码 JSON 格式和 Go语言的二进制格式相比,我们处理 XML 格式时必须写更多的代码。(该问题有望在 Go 1.x 发行版中得以解决。)
这里有个简单的 XML 格式的发票文件。为了适应页面的宽度和容易阅读,我们添加了换行和额外的空白。
type XMLInvoices struct { XMLName xml.Name 'xml:"INVOICES"' Version int 'xml:"version, attr"' Invoice []*XMLInvoice 'xml:"INVOICE"'}在 Go语言中,结构体的标签本质上没有任何语义,它们只是可以使用 Go语言的反射接口获得的字符串。然而,encoding/xml 包要求我们使用该标签来提供如何将结构体的字段映射到 XML 的信息。 typo Invoice struct { Id int Customerld int Raised time.Time Due time.Time Paid bool Note string Items []*1tem} type XMLInvoice struct { XMLName xml.Name 'xml: "INVOICE"' Id int 'xml:",attr"' CustomerId int 'xml:",attr"' Raised string 'xml:",attr"' Due string 'xml:",attr"' Paid bool 'xml:",attr"' Note string 'xml:"NOTE"' Item []*XMLItem 'xml:"ITEM"'}在这里,我们为属性提供了默认的名字。例如,字段 Customerld 在 XML 中对应一个属性,其名字与该字段的名字完全一样。这里有两个可嵌套的标签:<NOTE>和<ITEM>,并且如 XMLInvoices 结构体一样,我们把 XMLInvoice 的 item 字段定义成 Item(大小写不敏感)而非 Items,以匹配标签名。 type Item struct { Id string Price float64 Quantity int Note string} type XMLItem struct { XMLName xml.Name 'xml:"ITEM"' Id string 'xml:",attr"' Price float64 'xml:",attr"' Quantity int 'xml:",attr"' Note string 'xml:"NOTE"'}除了作为嵌套的 <NOTE> 标签的 Note 字段和用于保存该 XML 标签名的 XMLName 字段之外,XMLItem 的字段都被打上了标签以作为属性。 type XMLMarshaler struct{} func (XMLMarshaler) Marshallnvoices(writer io.Writer, invoices []*Invoice) error { if err := writer.Writer([]byte(xml.Header)); err != nil { return err } xmlinvoices := XMLInvoicesForInvoices(invoices) encoder := xml.NewEncoder(writer) return encoder.Encode(xmlinvoices)}该方法接受一个 io.Writer (也就是说,任何满足 io.Writer 接口的值如打开的文件或者打开的压缩文件),以用于写入 XML 数据。该方法从写入标准的 XML 头部开始 ( 该 xml.Header 常量的末尾包含一个新行)。 func XMLInvoicesForlnvoices(invoices []*Invoice) *XMLInvoices { xmlinvoices :=• &XMLInvoices{ Version: fileVersion, Invoice: make([]*XMLInvoice, 0, len(invoices)), } for _, invoice := range invoices { xmlInvoices.Invoice = append(xmlinvoices.Invoice, XMLInvoiceForInvoice (invoice)) } return xmlInvoices}该函数接受一个 []*Invoice 值并返回一个 *XMLInvoices 值,其中包含转换成 *XMLInvoices(还包含 *XMLItems 而非 *Items) 的所有数据。该函数又依赖于 XmlInvoiceForInvoice() 函数来为其完成所有工作。 func XMLInvoiceForlnvoice(invoice *Invoice) *XMLInvoice { xmlInvoice := &XMLInvoice{ Id: invoice.id, CustomerId: invoice.CustomerId, Raised: invoice.Raised.Format(dateFormat), Due: invoice.Due.Format(dateFormat), Paid: invoice.Paid, Note: invoice.Note, Item: make([]*XMLItem, 0, len(invoice.Items)), } for _, item := range invoice.Items { xmlItem := &XMLItem { Id: item.Id, Price: item.Price, Quantity: item.Quantity, Note: item.Note, } xmlInvoice.Item = append(xmlInvoice.Item, xmlItem) } return xmlInvoice}该函数接受一个 Invoice 值并返回一个等价的 XMLInvoice 值。该转换非常直接,只需简单地将 Invoice 中每个字段的值复制至 XMLInvoice 字段中。由于我们选择自己来处理创建以及到期日期(因此我们只需存储日期而非完整的日期/时间),我们只需将其转换成字符串。 func (XMLMarshaler) UnmarshalInvoices(reader io.Reader)([]*Invoice, error) { xmlInvoices := &XMLInvoices{} decoder := xml.NewDecoder(reader) if err := decoder.Decode(xmlInvoices); err != nil { return nil, err } if xmlInvoices.Version > fileVersion { return nil, fmt.Errorf ("version %d is too new to read", xmlInvoices.Version) } return xmlInvoices.Invoices()}该方法接受一个 io.Reader( 也就是说,任何满足 io.Reader 接口的值如打开的文件或者打开的压缩文件),并从其中读取 XML。该方法的开始处创建了一个指向空 XMLInvoices 结构体的指针,以及一个 xml.Decoder 用于读取 io.Reader。 func (xmllnvoices *XMLInvoices) Invoices() (invoices []*Invoice, err error){ invoices = make([]*Invoice, 0, len(xmllnvoices.Invoice)) for _, XMLInvoice := range xmlInvoices.Invoice { invoice, err := xmlInvoice.Invoice() if err != nil { return nil, err } invoices = append(invoices, invoice) } return invoices, nil}该 XMLInvoices.Invoices() 方法将一个 *XMLInvoices 值转换成一个 []*Invoice 值,它是 XmllnvoicesForInvoices() 函数的逆反操作,并将具体的转换工作交给 XMLInvoice.Invoice() 方法完成。 func (xmlInvoice *XMLInvoice) Invoice() (invoice *Invoice, err error) { invoice = &Invoice{ Id: xmlInvoice.Id, CustomerId: xmlInvoice.CustomerId, Paid: xmlInvoice.Paid, Note: strings.TrimSpace(xmlInvoice.Note), ems: make([]*Item, 0, len(xmlInvoice.Item)), } if invoice.Raised, err = time.Parse(dateFormat, xmlInvoice.Raised); err != nil { return nil, err } if invoice.Due, err = time.Parse(dateFormat, xmlInvoice.Due); err != nil{ return nil, err } for _, xmlItem := range xmlInvoice.Item { item := &Item { Id: xmlItem.Id, Price: xmlItem.Price, Quantity: xmlItem.Quantity, Note: strings.TrimSpace(xmlItem.Note), } invoice.Items = append(invoice.Items, item) } return invoice, nil}该方法用于返回与调用它的 *XMLInvoice 值相应的 *Invoice 值。 以上就是《Go语言XML文件的读写操作》的详细内容,更多关于golang的资料请关注golang学习网公众号!写 XML 文件
encoidng/xml 包要求我们使用的结构体中的字段包含 encoding/xml 包中所声明的标签,所以我们不能直接将 Invoice 和 Item 结构体用于 XML 序列化。
因此,我们创建了针对 XML 格式的 XMLInvoices、XMLInvoice 和 XMLItem 结构体来解决这个问题。同时,由于 invoicedata 程序要求我们有并行的结构体集合,因此必须提供一种方式来让它们相互转换。
当然,使用 XML 格式作为主要存储格式的应用程序只需一个结构体(或者一个结构体集合),同时要将必要的 encoidng/xml 包的标签直接添加到结构体的字段中。
下面是保存整个数据集合的 XMLInvoices 结构体。
xml.Name 字段用于为 XML 中的标签命名,该标签包含了该字段所在的结构体。以 'xml:",attr"' 标记的字段将成为该标签的属性,字段名字将成为属性名。我们也可以根据自己的喜好使用另一个名字,只需在所给的名字签名加上一个逗号。
这里,我们把 Version 字段当做一个叫做 version 的属性,而非默认的名字 Version。如果标签只包含一个名字,则该名字用于表示嵌套的标签,如此例中的 <TNVOTCE> 标签。有一个非常重要的细节需注意的是,我们把 XMLInvoices 的发票字段命名为 Invoice,而非 Invoices,这是为了匹配 XML 格式中的标签名(不区分大小写)。
下面是原始的 Invoice 结构体,以及与 XML 格式相对应的 XMLInvoice 结构体。
由于我们希望自己处理创建和到期日期(只存储日期),而非让 encoding/xml 包来保存完整的日期/时间字符串,我们为它们在 XMLInvoice 结构体中定义了相应的 Raised 和 Due 字段。
下面是原始的 Item 结构体,以及与 XML 相对应的 XMLItem 结构体。
正如处理 JSON 格式时所做的那样,对于 XML 格式,我们创建了一个空的结构体并关联了 XML 相关的 MarshalInvoices() 方法和 UnmarshalInvoices() 方法。
然后,它将所有的发票数据及其项写入相应的 XML 结构体中。这样做虽然看起来会耗费与原始数据相同的内存,但是由于 Go语言的字符串是不可变的,因此在底层只将原始数据字符串的引用复制到 XML 结构体中,因此其代价并不是我们所看到的那么大。而对于直接使用带有 XML 标签的结构体的应用而言,其数据没必要再次转换。
一旦填充好 xmlInvoices( 其类型为 XMLInvoices) 后,我们创建了一个新的 xml.Encoder,并将我们希望写入数据的 io.Writer 传给它。然后,我们将数据编码成 XML 格式,并返回编码器的返回值,该值可能为一个 error 值也可能为 nil。
我们不必手动填充 xml.Name 字段(除非我们想使用名字空间),因此在这里,当创建 *XMLInvoices 的时候,我们只需填充 Version 字段以保证我们的标签有一个 version 属性,例如 <INVILES verion="100">。
同时,我们将 Invoice 字段设置成一个空间足够容纳所有的发票数据的空切片。这样做不是严格必须的,但是与将该字段的初始值留空相比,这样做可能更高效,因为这样做意味着调用内置的 append() 函数时无需分配内存和复制数据以扩充切片容量。
而对于 Invoice.Items 字段,我们将每一项转换成 XMLItem 后添加到 XMLInvoice.Item 切片中。与前面一样,我们使用相同的优化方式,创建 Item 切片时分配了足够多的空间以避免 append() 时需要分配内存和复制数据。前文阐述 JSON 格式时我们已讨论过 time.Time 值的写入。
最后需要注意的是,我们的代码中没有做任何 XML 转义,它是由 xml.Encoder.Encode() 方法自动完成的。读 XML 文件
读 XML 文件比写 XML 文件稍微复杂,特别是在必须处理一些我们自定义的字段的时候(例如日期)。但是,如果我们使用合理的打上 XML 标签的结构体,就不会复杂。
然后,整个 XML 文件由 xml.Decoder.Decode() 方法解析,如果解析成功则将 XML 文件的数据填充到该 *XMLInvoices 结构体中。如果解析失败(例如,XML 文件语法有误,或者该文件不是一个合法的发票文件),那么解码器会立即返回错误值给调用者。
如果解析成功,我们再检查其版本,如果该版本是我们能够处理的,就将该 XML 结构体转换成我们程序内部使用的结构体。当然,如果我们直接使用带 XML 标签的结构体,该转换步骤就没必要了。
该方法在开始处创建了一个 Invoice 值,其大部分字段都由来自 XMLInvoice 的数据填充,而 Items 字段则设置成一个容量足够大的空切片。
然后,由于我们选择自己处理这些,因此手动填充两个日期/时间字段。time.Parse() 函数接受一个日期/时间格式的字符串(如前所述,该字符串必须基于精确的日期/时间值,如 2006-01-02T15:04:05Z07:00),以及一个需要解析的字符串,并返回等价的 time.Time 值和 nil,或者,返回一个 nil 和一个错误值。
接下来是填充发票的 Items 字段,这是通过迭代 XMLInvoice 的 Item 字段中的 *XMLItems 并创建相应的 *Items 来完成的。最后,返回 *Invoice。
正如写 XML 时一样,我们无需关心对所读取的 XML 数据进行转义,xml.Decoder.Decode() 函数会自动处理这些。
xml 包支持比我们这里所需的更为复杂的标签,包括嵌套。例如,标签名为 'xml:"Books>Author"' 产生的是
-
225 收藏
-
389 收藏
-
250 收藏
-
137 收藏
-
190 收藏
-
113 收藏
-
140 收藏
-
277 收藏
-
186 收藏
-
346 收藏
-
286 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习
-
- 过时的吐司
- 这篇技术文章真是及时雨啊,好细啊,很好,码起来,关注up主了!希望up主能多写Golang相关的文章。
- 2023-05-04 23:49:07
-
- 激情的鸭子
- 这篇技术贴出现的刚刚好,很详细,感谢大佬分享,mark,关注作者了!希望作者能多写Golang相关的文章。
- 2023-01-26 18:09:07
-
- 小巧的柚子
- 这篇文章内容真是及时雨啊,太细致了,很棒,收藏了,关注作者大大了!希望作者大大能多写Golang相关的文章。
- 2023-01-14 16:41:40
-
- 大瓶可乐
- 很好,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢师傅分享文章内容!
- 2023-01-13 06:32:22