diff --git a/gpx/converters.go b/gpx/converters.go index 13acbd7..1946e28 100644 --- a/gpx/converters.go +++ b/gpx/converters.go @@ -322,12 +322,9 @@ func NewGPXAttributes(attrs []xml.Attr) GPXAttributes { namespacesByUrls[attr.Value] = attr.Name.Local } } - fmt.Printf("namespaces: %#v\n", namespacesByUrls) res := map[string]map[string]Attr{} for _, attr := range attrs { - // fmt.Println("space=", attr.Name.Space) - // fmt.Println("local=", attr.Name.Local) space := attr.Name.Space if ns, found := namespacesByUrls[attr.Name.Space]; found { space = ns diff --git a/gpx/gpx11.go b/gpx/gpx11.go index a748950..31855b7 100644 --- a/gpx/gpx11.go +++ b/gpx/gpx11.go @@ -7,200 +7,8 @@ package gpx import ( "encoding/xml" - "strings" ) -type Node struct { - XMLName xml.Name - Attrs []xml.Attr `xml:",any,attr"` - Data string `xml:",chardata"` - Nodes []Node `xml:",any"` -} - -func (n Node) debugXMLChunk() []byte { - byts, err := xml.MarshalIndent(n, "", " ") - if err != nil { - return []byte("???") - } - return byts -} - -func (n Node) toTokens(prefix string) (tokens []xml.Token) { - var attrs []xml.Attr - for _, a := range n.Attrs { - attrs = append(attrs, xml.Attr{Name: xml.Name{Local: prefix + a.Name.Local}, Value: a.Value}) - } - - start := xml.StartElement{Name: xml.Name{Local: prefix + n.XMLName.Local, Space: ""}, Attr: attrs} - tokens = append(tokens, start) - data := strings.TrimSpace(n.Data) - if len(n.Nodes) > 0 { - for _, node := range n.Nodes { - tokens = append(tokens, node.toTokens(prefix)...) - } - } else if data != "" { - tokens = append(tokens, xml.CharData(data)) - } else { - return nil - } - tokens = append(tokens, xml.EndElement{start.Name}) - return -} - -func (n *Node) GetAttr(key string) (value string, found bool) { - for i := range n.Attrs { - if n.Attrs[i].Name.Local == key { - value = n.Attrs[i].Value - found = true - return - } - } - return -} - -func (n *Node) SetAttr(key, value string) { - for i := range n.Attrs { - if n.Attrs[i].Name.Local == key { - n.Attrs[i].Value = value - return - } - } - n.Attrs = append(n.Attrs, xml.Attr{ - Name: xml.Name{ - Space: n.SpaceNameURL(), - Local: key, - }, - Value: value, - }) -} - -func (n *Node) GetNode(path0 string) (node *Node, found bool) { - for subn := range n.Nodes { - if n.Nodes[subn].LocalName() == path0 { - node = &n.Nodes[subn] - found = true - return - } - } - return -} - -func (n *Node) getOrCreateNode(path ...string) *Node { - if len(path) == 0 { - return n - } - - path0, rest := path[0], path[1:] - - subNode, found := n.GetNode(path0) - if !found { - n.Nodes = append(n.Nodes, Node{ - XMLName: xml.Name{ - Space: n.XMLName.Space, - Local: path0, - }, - Attrs: nil, - }) - subNode = &(n.Nodes[len(n.Nodes)-1]) - } - - return subNode.getOrCreateNode(rest...) -} - -func (n Node) IsEmpty() bool { return len(n.Nodes) == 0 && len(n.Attrs) == 0 && len(n.Data) == 0 } -func (n Node) LocalName() string { return n.XMLName.Local } -func (n Node) SpaceNameURL() string { return n.XMLName.Space } -func (n Node) GetAttrOrEmpty(attr string) string { - val, _ := n.GetAttr(attr) - return val -} - -type Extension struct { - // XMLName xml.Name - // Attrs []xml.Attr `xml:",any,attr"` - Nodes []Node `xml:",any"` - - // Filled before deserializing: - globalNsAttrs map[string]Attr -} - -var _ xml.Marshaler = Extension{} - -func (ex Extension) debugXMLChunk() []byte { - byts, err := xml.MarshalIndent(ex, "", " ") - if err != nil { - return []byte("???") - } - return byts -} - -func (ex Extension) MarshalXML(e *xml.Encoder, start xml.StartElement) error { - if len(ex.Nodes) == 0 { - return nil - } - - start = xml.StartElement{Name: xml.Name{Local: start.Name.Local}, Attr: nil} - tokens := []xml.Token{start} - for _, node := range ex.Nodes { - prefix := "" - for _, v := range ex.globalNsAttrs { - if node.SpaceNameURL() == v.Value || node.SpaceNameURL() == v.Name.Local { - prefix = v.replacement - } - } - tokens = append(tokens, node.toTokens(prefix)...) - } - - tokens = append(tokens, xml.EndElement{Name: start.Name}) - - for _, t := range tokens { - err := e.EncodeToken(t) - if err != nil { - return err - } - } - - // flush to ensure tokens are written - err := e.Flush() - if err != nil { - return err - } - - return nil -} - -func (ex *Extension) GetOrCreateNode(namespaceURL string, path ...string) *Node { - // TODO: Check is len(nodes) == 0 - var subNode *Node - for n := range ex.Nodes { - if ex.Nodes[n].SpaceNameURL() == namespaceURL && ex.Nodes[n].LocalName() == path[0] { - subNode = &ex.Nodes[n] - break - } - } - if subNode == nil { - ex.Nodes = append(ex.Nodes, Node{ - XMLName: xml.Name{ - Space: namespaceURL, - Local: path[0], - }, - }) - subNode = &ex.Nodes[len(ex.Nodes)-1] - } - return subNode.getOrCreateNode(path[1:]...) -} - -func (ex *Extension) GetNode(path0 string) (node *Node, found bool) { - for subn := range ex.Nodes { - if ex.Nodes[subn].LocalName() == path0 { - node = &ex.Nodes[subn] - found = true - return - } - } - return -} - /* The GPX XML hierarchy: diff --git a/gpx/gpx11_extensions.go b/gpx/gpx11_extensions.go new file mode 100644 index 0000000..a19975a --- /dev/null +++ b/gpx/gpx11_extensions.go @@ -0,0 +1,199 @@ +package gpx + +import ( + "encoding/xml" + "strings" +) + +type ExtensionNode struct { + XMLName xml.Name + Attrs []xml.Attr `xml:",any,attr"` + Data string `xml:",chardata"` + Nodes []ExtensionNode `xml:",any"` +} + +func (n ExtensionNode) debugXMLChunk() []byte { + byts, err := xml.MarshalIndent(n, "", " ") + if err != nil { + return []byte("???") + } + return byts +} + +func (n ExtensionNode) toTokens(prefix string) (tokens []xml.Token) { + var attrs []xml.Attr + for _, a := range n.Attrs { + attrs = append(attrs, xml.Attr{Name: xml.Name{Local: prefix + a.Name.Local}, Value: a.Value}) + } + + start := xml.StartElement{Name: xml.Name{Local: prefix + n.XMLName.Local, Space: ""}, Attr: attrs} + tokens = append(tokens, start) + data := strings.TrimSpace(n.Data) + if len(n.Nodes) > 0 { + for _, node := range n.Nodes { + tokens = append(tokens, node.toTokens(prefix)...) + } + } else if data != "" { + tokens = append(tokens, xml.CharData(data)) + } else { + return nil + } + tokens = append(tokens, xml.EndElement{start.Name}) + return +} + +func (n *ExtensionNode) GetAttr(key string) (value string, found bool) { + for i := range n.Attrs { + if n.Attrs[i].Name.Local == key { + value = n.Attrs[i].Value + found = true + return + } + } + return +} + +func (n *ExtensionNode) SetAttr(key, value string) { + for i := range n.Attrs { + if n.Attrs[i].Name.Local == key { + n.Attrs[i].Value = value + return + } + } + n.Attrs = append(n.Attrs, xml.Attr{ + Name: xml.Name{ + Space: n.SpaceNameURL(), + Local: key, + }, + Value: value, + }) +} + +func (n *ExtensionNode) GetNode(path0 string) (node *ExtensionNode, found bool) { + for subn := range n.Nodes { + if n.Nodes[subn].LocalName() == path0 { + node = &n.Nodes[subn] + found = true + return + } + } + return +} + +func (n *ExtensionNode) getOrCreateNode(path ...string) *ExtensionNode { + if len(path) == 0 { + return n + } + + path0, rest := path[0], path[1:] + + subNode, found := n.GetNode(path0) + if !found { + n.Nodes = append(n.Nodes, ExtensionNode{ + XMLName: xml.Name{ + Space: n.XMLName.Space, + Local: path0, + }, + Attrs: nil, + }) + subNode = &(n.Nodes[len(n.Nodes)-1]) + } + + return subNode.getOrCreateNode(rest...) +} + +func (n ExtensionNode) IsEmpty() bool { + return len(n.Nodes) == 0 && len(n.Attrs) == 0 && len(n.Data) == 0 +} +func (n ExtensionNode) LocalName() string { return n.XMLName.Local } +func (n ExtensionNode) SpaceNameURL() string { return n.XMLName.Space } +func (n ExtensionNode) GetAttrOrEmpty(attr string) string { + val, _ := n.GetAttr(attr) + return val +} + +type Extension struct { + // XMLName xml.Name + // Attrs []xml.Attr `xml:",any,attr"` + Nodes []ExtensionNode `xml:",any"` + + // Filled before deserializing: + globalNsAttrs map[string]Attr +} + +var _ xml.Marshaler = Extension{} + +func (ex Extension) debugXMLChunk() []byte { + byts, err := xml.MarshalIndent(ex, "", " ") + if err != nil { + return []byte("???") + } + return byts +} + +func (ex Extension) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + if len(ex.Nodes) == 0 { + return nil + } + + start = xml.StartElement{Name: xml.Name{Local: start.Name.Local}, Attr: nil} + tokens := []xml.Token{start} + for _, node := range ex.Nodes { + prefix := "" + for _, v := range ex.globalNsAttrs { + if node.SpaceNameURL() == v.Value || node.SpaceNameURL() == v.Name.Local { + prefix = v.replacement + } + } + tokens = append(tokens, node.toTokens(prefix)...) + } + + tokens = append(tokens, xml.EndElement{Name: start.Name}) + + for _, t := range tokens { + err := e.EncodeToken(t) + if err != nil { + return err + } + } + + // flush to ensure tokens are written + err := e.Flush() + if err != nil { + return err + } + + return nil +} + +func (ex *Extension) GetOrCreateNode(namespaceURL string, path ...string) *ExtensionNode { + // TODO: Check is len(nodes) == 0 + var subNode *ExtensionNode + for n := range ex.Nodes { + if ex.Nodes[n].SpaceNameURL() == namespaceURL && ex.Nodes[n].LocalName() == path[0] { + subNode = &ex.Nodes[n] + break + } + } + if subNode == nil { + ex.Nodes = append(ex.Nodes, ExtensionNode{ + XMLName: xml.Name{ + Space: namespaceURL, + Local: path[0], + }, + }) + subNode = &ex.Nodes[len(ex.Nodes)-1] + } + return subNode.getOrCreateNode(path[1:]...) +} + +func (ex *Extension) GetNode(path0 string) (node *ExtensionNode, found bool) { + for subn := range ex.Nodes { + if ex.Nodes[subn].LocalName() == path0 { + node = &ex.Nodes[subn] + found = true + return + } + } + return +} diff --git a/gpx/gpx_test.go b/gpx/gpx_test.go index 4cba5dd..cddfef6 100644 --- a/gpx/gpx_test.go +++ b/gpx/gpx_test.go @@ -1515,7 +1515,7 @@ func TestExtensionWithoutNamespace(t *testing.T) { func TestNodesSubnodesAndAttrs(t *testing.T) { t.Parallel() - var node Node + var node ExtensionNode assert.Equal(t, 0, len(node.Attrs)) node.SetAttr("xxx", "yyy")