diff --git a/pres_muc.go b/pres_muc.go index b61f0ae..f7ae599 100644 --- a/pres_muc.go +++ b/pres_muc.go @@ -2,6 +2,7 @@ package xmpp import ( "encoding/xml" + "strconv" "time" ) @@ -16,12 +17,130 @@ type MucPresence struct { History History `xml:"history,omitempty"` } +const timeLayout = "2006-01-02T15:04:05Z" + // History implements XEP-0045: Multi-User Chat - 19.1 type History struct { - MaxChars *int `xml:"maxchars,attr,omitempty"` - MaxStanzas *int `xml:"maxstanzas,attr,omitempty"` - Seconds *int `xml:"seconds,attr,omitempty"` - Since *time.Time `xml:"since,attr,omitempty"` + XMLName xml.Name + MaxChars NullableInt `xml:"maxchars,attr,omitempty"` + MaxStanzas NullableInt `xml:"maxstanzas,attr,omitempty"` + Seconds NullableInt `xml:"seconds,attr,omitempty"` + Since time.Time `xml:"since,attr,omitempty"` +} + +type NullableInt struct { + Value int + isSet bool +} + +func NewNullableInt(val int) NullableInt { + return NullableInt{val, true} +} + +func (n NullableInt) Get() (v int, ok bool) { + return n.Value, n.isSet +} + +// UnmarshalXML implements custom parsing for history element +func (h *History) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + h.XMLName = start.Name + + // Extract attributes + for _, attr := range start.Attr { + switch attr.Name.Local { + case "maxchars": + v, err := strconv.Atoi(attr.Value) + if err != nil { + return err + } + h.MaxChars = NewNullableInt(v) + case "maxstanzas": + v, err := strconv.Atoi(attr.Value) + if err != nil { + return err + } + h.MaxStanzas = NewNullableInt(v) + case "seconds": + v, err := strconv.Atoi(attr.Value) + if err != nil { + return err + } + h.Seconds = NewNullableInt(v) + case "since": + t, err := time.Parse(timeLayout, attr.Value) + if err != nil { + return err + } + h.Since = t + } + } + + // Consume remaining data until element end + for { + t, err := d.Token() + if err != nil { + return err + } + + switch tt := t.(type) { + case xml.EndElement: + if tt == start.End() { + return nil + } + } + } +} + +func (h History) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { + mc, isMcSet := h.MaxChars.Get() + ms, isMsSet := h.MaxStanzas.Get() + s, isSSet := h.Seconds.Get() + + // We do not have any value, ignore history element + if h.Since.IsZero() && !isMcSet && !isMsSet && !isSSet { + return nil + } + + // Encode start element and attributes + start.Name = xml.Name{Local: "history"} + + if isMcSet { + attr := xml.Attr{ + Name: xml.Name{Local: "maxchars"}, + Value: strconv.Itoa(mc), + } + start.Attr = append(start.Attr, attr) + } + + if isMsSet { + attr := xml.Attr{ + Name: xml.Name{Local: "maxstanzas"}, + Value: strconv.Itoa(ms), + } + start.Attr = append(start.Attr, attr) + } + + if isSSet { + attr := xml.Attr{ + Name: xml.Name{Local: "seconds"}, + Value: strconv.Itoa(s), + } + start.Attr = append(start.Attr, attr) + } + + if !h.Since.IsZero() { + attr := xml.Attr{ + Name: xml.Name{Local: "since"}, + Value: h.Since.Format(timeLayout), + } + start.Attr = append(start.Attr, attr) + } + if err := e.EncodeToken(start); err != nil { + return err + } + + return e.EncodeToken(xml.EndElement{Name: start.Name}) + } func init() { diff --git a/pres_muc_test.go b/pres_muc_test.go index 532bc0a..5b8db83 100644 --- a/pres_muc_test.go +++ b/pres_muc_test.go @@ -46,16 +46,18 @@ func TestMucHistory(t *testing.T) { var parsedPresence xmpp.Presence if err := xml.Unmarshal([]byte(str), &parsedPresence); err != nil { - t.Errorf("Unmarshal(%s) returned error", str) + t.Errorf("Unmarshal(%s) returned error: %s", str, err) + return } var muc xmpp.MucPresence if ok := parsedPresence.Get(&muc); !ok { t.Error("muc presence extension was not found") + return } - if *muc.History.MaxStanzas != 20 { - t.Errorf("incorrect max stanza: '%d'", muc.History.MaxStanzas) + if v, ok := muc.History.MaxStanzas.Get(); !ok || v != 20 { + t.Errorf("incorrect MaxStanzas: '%#v'", muc.History.MaxStanzas) } } @@ -79,13 +81,14 @@ func TestMucNoHistory(t *testing.T) { }, Extensions: []xmpp.PresExtension{ xmpp.MucPresence{ - History: xmpp.History{MaxStanzas: &maxstanzas}, + History: xmpp.History{MaxStanzas: xmpp.NewNullableInt(maxstanzas)}, }, }, } data, err := xml.Marshal(&pres) if err != nil { t.Error("error on encode:", err) + return } if string(data) != str {