Some initial experimentation with SCPD reading.
This commit is contained in:
161
v2alpha/description/xmlscpd/xmlscpd.go
Normal file
161
v2alpha/description/xmlscpd/xmlscpd.go
Normal file
@ -0,0 +1,161 @@
|
||||
// Package xmlscpd 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 xmlscpd
|
||||
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user