Skip to content

Instantly share code, notes, and snippets.

@treywelsh
Last active November 24, 2023 03:06
Show Gist options
  • Save treywelsh/5486bc3af9ee12e415ee82f1ec1997dc to your computer and use it in GitHub Desktop.
Save treywelsh/5486bc3af9ee12e415ee82f1ec1997dc to your computer and use it in GitHub Desktop.
golang dynamic xml unmarshalling mixed with fixed tags
package main
import (
"encoding/xml"
"fmt"
pkg "xml_unparsed/xmlmap"
//pkg "xml_unparsed/xmlslice"
)
type xmlTest struct {
Tag4 string `xml:"tag4"`
Unparsed pkg.UnparsedTags `xml:",any"`
}
func main() {
xmlstr := `<foo><tag1>toto</tag1> <tag2>tutu</tag2> <foo2><tag3>titi</tag3></foo2> <tag4>parsed</tag4> <tag5>blabla</tag5></foo>`
var xmlstruct xmlTest
err := xml.Unmarshal([]byte(xmlstr), &xmlstruct)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(xmlstr)
fmt.Printf("resp: %+v\n", xmlstruct)
fetched := xmlstruct.Unparsed.GetContentByName("tag5")
if len(fetched) > 0 {
fmt.Printf("Getted by name: %+v\n", fetched)
}
}
package xmlmap
import (
"encoding/xml"
//"fmt"
)
// Store unparsed tags in a map (useful with flat dynamic xml).
// Inspired from: https://stackoverflow.com/questions/30928770/marshall-map-to-xml-in-go/33110881
// The main difference is that it can be mixed with defined tags in a struct. For each unparsed element UnmarshalXML is called once.
// NOTE: to be used in flat xml part with distinct tag names
// If it's not flat: the hash will contains key with empty values
// If there is several tags with the same name : only the last value will be stored
// UnparsedTag contains the tag informations
type UnparsedTag struct {
XMLName xml.Name
Content string `xml:",chardata"`
//FullContent string `xml:",innerxml"` // for debug purpose, allow to see what's inside some tags
}
// UnparsedTags store tags not handled by Unmarshal in a map, it should be labelled with `xml",any"`
type UnparsedTags map[string]string
func (m *UnparsedTags) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
if *m == nil {
*m = UnparsedTags{}
}
e := UnparsedTag{}
err := d.DecodeElement(&e, &start)
if err != nil {
return err
}
//if _, ok := (*m)[e.XMLName.Local]; ok {
// return fmt.Errorf("UnparsedTags: UnmarshalXML: Tag %s: multiple entries with the same name", e.XMLName.Local)
//}
(*m)[e.XMLName.Local] = e.Content
return nil
}
func (u *UnparsedTags) GetContentByName(name string) string {
return ((map[string]string)(*u))[name]
}
package xmlslice
import "encoding/xml"
// Store unparsed tags in a map
// Difference with xmlmap: there is no unicity required on the tag name anymore
// UnparsedTags contains a list of tags not handled by unmarshall, it should be marked with `xml",any"`
type UnparsedTags struct {
Tags []UnparsedTag
}
// UnparsedTag contains the tag informations
type UnparsedTag struct {
XMLName xml.Name
Content string `xml:",chardata"`
//FullContent string `xml:",innerxml"` // for debug purpose, allow to what's inside some tags
}
func (u *UnparsedTags) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var e UnparsedTag
err := d.DecodeElement(&e, &start)
if err != nil {
return err
}
u.Tags = append(u.Tags, e)
return nil
}
func (u *UnparsedTags) GetContentByName(name string) []string {
content := make([]string, 0, 1)
for _, t := range u.Tags {
if t.XMLName.Local != name {
continue
}
content = append(content, t.Content)
}
return content
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment