I'm writing XML from the following struct:
type OrderLine struct {
LineNumber string `xml:"LineNumber"`
Product string `xml:"Product"`
Ref string `xml:"Ref"`
Quantity string `xml:"Quantity"`
Price string `xml:"Price"`
LineTotalGross string `xml:"LineTotalGross"`
}
If the Ref field is empty, I'd like the element to display, but be self-closing, i.e.
<Ref />
and not:
<Ref></Ref>
AFAIK, these two are semantically equivalent, but I would prefer a self-closing tag, as it matches the output from other systems. Is this possible?
This post provides two solutions that do not rely on regexp and explains the differences between the two.
The first version is memory friendly, but cpu adverse. It implements a writer that replaces occurrences of search by replace within buffered bytes. It tries to write data as soon as possible, preventing large allocation in memory. It is not the best usage of the cpu because if will scan same data multiple times.
package main
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"os"
)
// Person represents a <person> node in the XML
type Person struct {
XMLName xml.Name `xml:"Players"`
DataItems []dataItem `xml:"DataItem"`
}
// Skill represents a <skill> node in the XML
type dataItem struct {
XMLName xml.Name `xml:"DataItem"`
Name string `xml:"skillName,attr"`
YearsPracticed int64 `xml:"practice,attr"`
Level string `xml:"level,attr"`
}
func main() {
players := Person{
DataItems: []dataItem{
{Name: "Soccer", YearsPracticed: 3, Level: "Newbie"},
{Name: "Basketball", YearsPracticed: 4, Level: "State"},
{Name: "Baseball", YearsPracticed: 10, Level: "National"},
},
}
players.DataItems = append(players.DataItems, players.DataItems...)
players.DataItems = append(players.DataItems, players.DataItems...)
players.DataItems = append(players.DataItems, players.DataItems...)
players.DataItems = append(players.DataItems, players.DataItems...)
players.DataItems = append(players.DataItems, players.DataItems...)
dst := &Replace{
Writer: os.Stdout,
Search: []byte("></DataItem>"),
Replace: []byte("/>"),
}
defer dst.Flush()
enc := xml.NewEncoder(dst)
enc.Indent("", " ")
if err := enc.Encode(players); err != nil {
fmt.Printf("error: %v\n", err)
}
}
type Replace struct {
io.Writer
Search []byte
Replace []byte
buf []byte
}
func (s *Replace) Write(b []byte) (n int, err error) {
s.buf = append(s.buf, b...)
s.buf = bytes.ReplaceAll(s.buf, s.Search, s.Replace)
if len(s.buf) > len(s.Search) {
w := s.buf[:len(s.buf)-len(s.Search)]
n, err = s.Writer.Write(w)
s.buf = s.buf[n:]
}
return len(b), err
}
func (s *Replace) Flush() (err error) {
var n int
n, err = s.Writer.Write(s.buf)
s.buf = s.buf[n:]
return
}
The second version is cpu friendly but memory adverse, as it loads the entire data to modify in memory.
package main
import (
"bytes"
"encoding/xml"
"fmt"
"os"
)
// Person represents a <person> node in the XML
type Person struct {
XMLName xml.Name `xml:"Players"`
DataItems []dataItem `xml:"DataItem"`
}
// Skill represents a <skill> node in the XML
type dataItem struct {
XMLName xml.Name `xml:"DataItem"`
Name string `xml:"skillName,attr"`
YearsPracticed int64 `xml:"practice,attr"`
Level string `xml:"level,attr"`
}
func main() {
players := Person{
DataItems: []dataItem{
{Name: "Soccer", YearsPracticed: 3, Level: "Newbie"},
{Name: "Basketball", YearsPracticed: 4, Level: "State"},
{Name: "Baseball", YearsPracticed: 10, Level: "National"},
},
}
players.DataItems = append(players.DataItems, players.DataItems...)
players.DataItems = append(players.DataItems, players.DataItems...)
players.DataItems = append(players.DataItems, players.DataItems...)
players.DataItems = append(players.DataItems, players.DataItems...)
players.DataItems = append(players.DataItems, players.DataItems...)
out := new(bytes.Buffer)
enc := xml.NewEncoder(out)
enc.Indent("", " ")
if err := enc.Encode(players); err != nil {
fmt.Printf("error: %v\n", err)
}
b := bytes.ReplaceAll(out.Bytes(), []byte("></DataItem>"), []byte("/>"))
os.Stdout.Write(b)
}
Choose one according to your context of execution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With