goupnp/v2alpha/description/xmlsrvdesc/xmlsrvdesc.go

164 lines
4.4 KiB
Go

// Package xmlsrvdesc contains the XML data structures used in SCPD (Service Control Protocol
// Description).
//
// Described in section 2.5 of
// https://openconnectivity.org/upnp-specs/UPnP-arch-DeviceArchitecture-v2.0-20200417.pdf.
package xmlsrvdesc
import (
"bytes"
"encoding/xml"
"fmt"
"strings"
)
func cleanWhitespace(s *string) {
*s = strings.TrimSpace(*s)
}
// SCPD is the top level XML service description.
type SCPD struct {
XMLName xml.Name `xml:"scpd"`
ConfigId string `xml:"configId,attr"`
SpecVersion SpecVersion `xml:"specVersion"`
Actions []*Action `xml:"actionList>action"`
StateVariables []*StateVariable `xml:"serviceStateTable>stateVariable"`
}
// Clean removes stray whitespace in the structure.
//
// It's common for stray whitespace to be present in SCPD documents, this method removes them
// in-place.
func (scpd *SCPD) Clean() {
cleanWhitespace(&scpd.ConfigId)
for i := range scpd.Actions {
scpd.Actions[i].Clean()
}
for i := range scpd.StateVariables {
scpd.StateVariables[i].Clean()
}
}
// SpecVersion is part of a SCPD document, describes the version of the
// specification that the data adheres to.
type SpecVersion struct {
Major int32 `xml:"major"`
Minor int32 `xml:"minor"`
}
// Action XML description data.
type Action struct {
Optional PresenceBool
Name string `xml:"name"`
Arguments []*Argument `xml:"argumentList>argument"`
}
// Clean removes stray whitespace in the structure.
func (action *Action) Clean() {
cleanWhitespace(&action.Name)
for i := range action.Arguments {
action.Arguments[i].Clean()
}
}
// Argument XML data.
type Argument struct {
Name string `xml:"name"`
Direction string `xml:"direction"` // in|out
RelatedStateVariable string `xml:"relatedStateVariable"` // ?
Retval string `xml:"retval"` // ?
}
// Clean removes stray whitespace in the structure.
func (arg *Argument) Clean() {
cleanWhitespace(&arg.Name)
cleanWhitespace(&arg.Direction)
cleanWhitespace(&arg.RelatedStateVariable)
cleanWhitespace(&arg.Retval)
}
// StateVariable XML data.
type StateVariable struct {
Optional PresenceBool
Name string `xml:"name"`
SendEvents string `xml:"sendEvents,attr"` // yes|no
Multicast string `xml:"multicast,attr"` // yes|no
DataType DataType `xml:"dataType"`
DefaultValue string `xml:"defaultValue"`
AllowedValueRange *AllowedValueRange `xml:"allowedValueRange"`
AllowedValues []string `xml:"allowedValueList>allowedValue"`
}
// Clean removes stray whitespace in the structure.
func (v *StateVariable) Clean() {
cleanWhitespace(&v.Name)
cleanWhitespace(&v.SendEvents)
cleanWhitespace(&v.Multicast)
v.DataType.Clean()
cleanWhitespace(&v.DefaultValue)
if v.AllowedValueRange != nil {
v.AllowedValueRange.Clean()
}
for i := range v.AllowedValues {
cleanWhitespace(&v.AllowedValues[i])
}
}
// AllowedValueRange XML data.
type AllowedValueRange struct {
Minimum string `xml:"minimum"`
Maximum string `xml:"maximum"`
Step string `xml:"step"`
}
// Clean removes stray whitespace in the structure.
func (r *AllowedValueRange) Clean() {
cleanWhitespace(&r.Minimum)
cleanWhitespace(&r.Maximum)
cleanWhitespace(&r.Step)
}
// DataType XML data.
type DataType struct {
Name string `xml:",chardata"`
Type string `xml:"type,attr"`
}
// Clean removes stray whitespace in the structure.
func (dt *DataType) Clean() {
cleanWhitespace(&dt.Name)
cleanWhitespace(&dt.Type)
}
// PresenceBool represents an empty XML element that is true if present.
//
// Is an error if it contains any attributes or contents.
type PresenceBool bool
func (pb *PresenceBool) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
*pb = true
if len(start.Attr) > 0 {
return fmt.Errorf("unexpected attributes on element %s:%s",
start.Name.Space, start.Name.Local)
}
for {
tok, err := d.Token()
if err != nil {
return err
}
switch tok := tok.(type) {
case xml.CharData:
if len(bytes.TrimSpace([]byte(tok))) > 0 {
return fmt.Errorf("unexpected char data on element %s:%s",
start.Name.Space, start.Name.Local)
}
case xml.EndElement:
return nil
case xml.Comment:
default:
return fmt.Errorf("unexpected %T token on element %s:%s",
tok, start.Name.Space, start.Name.Local)
}
}
}