Reputation: 4254
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?
Upvotes: 20
Views: 5949
Reputation: 1
Until go v1.20.3 it's official package "encoding/xml" still not support shortform, so I modified it to support auto close tag. You can find it here. https://github.com/ECUST-XX/xml
Upvotes: 0
Reputation:
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.
Upvotes: 2
Reputation: 95
I found a way to do it "hacking" marshal package, but I didn't test it. If you want me to show you the link, let me now, then I post it in comments of this reply.
I did some manually code:
package main
import (
"encoding/xml"
"fmt"
"regexp"
"strings"
)
type ParseXML struct {
Person struct {
Name string `xml:"Name"`
LastName string `xml:"LastName"`
Test string `xml:"Abc"`
} `xml:"Person"`
}
func main() {
var err error
var newPerson ParseXML
newPerson.Person.Name = "Boot"
newPerson.Person.LastName = "Testing"
var bXml []byte
var sXml string
bXml, err = xml.Marshal(newPerson)
checkErr(err)
sXml = string(bXml)
r, err := regexp.Compile(`<([a-zA-Z0-9]*)><(\\|\/)([a-zA-Z0-9]*)>`)
checkErr(err)
matches := r.FindAllString(sXml, -1)
fmt.Println(sXml)
if len(matches) > 0 {
r, err = regexp.Compile("<([a-zA-Z0-9]*)>")
for i := 0; i < len(matches); i++ {
xmlTag := r.FindString(matches[i])
xmlTag = strings.Replace(xmlTag, "<", "", -1)
xmlTag = strings.Replace(xmlTag, ">", "", -1)
sXml = strings.Replace(sXml, matches[i], "<"+xmlTag+" />", -1)
}
}
fmt.Println("")
fmt.Println(sXml)
}
func checkErr(chk error) {
if chk != nil {
panic(chk)
}
}
Upvotes: 1