Stanzas can now contain multiple nested (extended) elements.
authorChris Jones <chris@cjones.org>
Sat, 07 Jan 2012 22:22:18 -0700
changeset 61 16513974d273
parent 60 6d4f43f7dc19
child 62 6e2eea62ccca
Stanzas can now contain multiple nested (extended) elements.
roster.go
roster_test.go
stream.go
structs.go
structs_test.go
xmpp.go
--- a/roster.go	Sat Jan 07 21:20:23 2012 -0700
+++ b/roster.go	Sat Jan 07 22:22:18 2012 -0700
@@ -52,7 +52,7 @@
 	rosterUpdate := rosterClients[client.Uid].rosterUpdate
 
 	iq := &Iq{From: client.Jid.String(), Id: <- Id, Type: "get",
-		Nested: RosterQuery{}}
+		Nested: []interface{}{RosterQuery{}}}
 	ch := make(chan os.Error)
 	f := func(st Stanza) bool {
 		defer close(ch)
@@ -60,8 +60,14 @@
 			ch <- iq.Error
 			return false
 		}
-		rq, ok := st.GetNested().(*RosterQuery)
-		if !ok {
+		var rq *RosterQuery
+		for _, ele := range(st.GetNested()) {
+			if q, ok := ele.(*RosterQuery) ; ok {
+				rq = q
+				break
+			}
+		}
+		if rq == nil {
 			ch <- os.NewError(fmt.Sprintf(
 				"Roster query result not query: %v", st))
 			return false
@@ -103,8 +109,14 @@
 func maybeUpdateRoster(client *Client, st Stanza) {
 	rosterUpdate := rosterClients[client.Uid].rosterUpdate
 
-	rq, ok := st.GetNested().(*RosterQuery)
-	if st.GetName() == "iq" && st.GetType() == "set" && ok {
+	var rq *RosterQuery
+	for _, ele := range(st.GetNested()) {
+		if q, ok := ele.(*RosterQuery) ; ok {
+			rq = q
+			break
+		}
+	}
+	if st.GetName() == "iq" && st.GetType() == "set" && rq != nil {
 		for _, item := range(rq.Item) {
 			rosterUpdate <- item
 		}
--- a/roster_test.go	Sat Jan 07 21:20:23 2012 -0700
+++ b/roster_test.go	Sat Jan 07 22:22:18 2012 -0700
@@ -14,7 +14,8 @@
 // This is mostly just tests of the roster data structures.
 
 func TestRosterIqMarshal(t *testing.T) {
-	iq := &Iq{From: "from", Lang: "en", Nested: RosterQuery{}}
+	iq := &Iq{From: "from", Lang: "en", Nested:
+		[]interface{}{RosterQuery{}}}
 	exp := `<iq from="from" xml:lang="en"><query xmlns="` +
 		NsRoster + `"></query></iq>`
 	assertMarshal(t, exp, iq)
@@ -26,7 +27,8 @@
 	r := strings.NewReader(str)
 	var st Stanza = &Iq{}
 	xml.Unmarshal(r, st)
-	err := parseExtended(st, newRosterQuery)
+	m := map[string] func(*xml.Name) interface{}{NsRoster: newRosterQuery}
+	err := parseExtended(st, m)
 	if err != nil {
 		t.Fatalf("parseExtended: %v", err)
 	}
@@ -37,7 +39,12 @@
 	if nested == nil {
 		t.Fatalf("nested nil")
 	}
-	rq, ok := nested.(*RosterQuery)
+	if len(nested) != 1 {
+		t.Fatalf("wrong size nested(%d): %v", len(nested),
+			nested)
+	}
+	var rq *RosterQuery
+	rq, ok := nested[0].(*RosterQuery)
 	if !ok {
 		t.Fatalf("nested not RosterQuery: %v",
 			reflect.TypeOf(nested))
--- a/stream.go	Sat Jan 07 21:20:23 2012 -0700
+++ b/stream.go	Sat Jan 07 22:22:18 2012 -0700
@@ -145,21 +145,15 @@
 			break
 		}
 
-		// If it's a Stanza, we check its "Any" element for a
-		// namespace that's registered with one of our
-		// extensions. If so, we need to re-unmarshal into an
-		// object of the correct type.
-		if st, ok := obj.(Stanza) ; ok && st.generic() != nil {
-			name := st.generic().XMLName
-			ns := name.Space
-			con := extStanza[ns]
-			if con != nil {
-				err = parseExtended(st, con)
-				if err != nil {
-					log.Printf("ext unmarshal: %v",
-						err)
-					break
-				}
+		// If it's a Stanza, we try to unmarshal its innerxml
+		// into objects of the appropriate respective
+		// types. This is specified by our extensions.
+		if st, ok := obj.(Stanza) ; ok {
+			err = parseExtended(st, extStanza)
+			if err != nil {
+				log.Printf("ext unmarshal: %v",
+					err)
+				break
 			}
 		}
 
@@ -168,35 +162,35 @@
 	}
 }
 
-func parseExtended(st Stanza, con func(*xml.Name) interface{}) os.Error {
-	name := st.generic().XMLName
-	nested := con(&name)
-
+func parseExtended(st Stanza, extStanza map[string] func(*xml.Name) interface{}) os.Error {
 	// Now parse the stanza's innerxml to find the string that we
 	// can unmarshal this nested element from.
 	reader := strings.NewReader(st.innerxml())
 	p := xml.NewParser(reader)
-	var start *xml.StartElement
 	for {
 		t, err := p.Token()
+		if err == os.EOF {
+			break
+		}
 		if err != nil {
 			return err
 		}
 		if se, ok := t.(xml.StartElement) ; ok {
-			if se.Name.Space == name.Space {
-				start = &se
-				break
+			if con, ok := extStanza[se.Name.Space] ; ok {
+				// Call the indicated constructor.
+				nested := con(&se.Name)
+
+				// Unmarshal the nested element and
+				// stuff it back into the stanza.
+				err := p.Unmarshal(nested, &se)
+				if err != nil {
+					return err
+				}
+				st.addNested(nested)
 			}
 		}
 	}
 
-	// Unmarshal the nested element and stuff it back into the
-	// stanza.
-	err := p.Unmarshal(nested, start)
-	if err != nil {
-		return err
-	}
-	st.setNested(nested)
 	return nil
 }
 
@@ -594,15 +588,21 @@
 	if res != "" {
 		bindReq.Resource = &res
 	}
-	msg := &Iq{Type: "set", Id: <- Id, Nested: &bindReq}
+	msg := &Iq{Type: "set", Id: <- Id, Nested: []interface{}{bindReq}}
 	f := func(st Stanza) bool {
 		if st.GetType() == "error" {
 			log.Println("Resource binding failed")
 			return false
 		}
-		bindRepl, ok := st.GetNested().(*bindIq)
-		if !ok {
-			log.Printf("bad bind reply: %v", bindRepl)
+		var bindRepl *bindIq
+		for _, ele := range(st.GetNested()) {
+			if b, ok := ele.(*bindIq) ; ok {
+				bindRepl = b
+				break
+			}
+		}
+		if bindRepl == nil {
+			log.Printf("bad bind reply: %v", st)
 			return false
 		}
 		jidStr := bindRepl.Jid
--- a/structs.go	Sat Jan 07 21:20:23 2012 -0700
+++ b/structs.go	Sat Jan 07 22:22:18 2012 -0700
@@ -93,11 +93,10 @@
 	GetLang() string
 	// A nested error element, if any.
 	GetError() *Error
-	// A (non-error) nested element, if any.
-	// BUG(cjyar) This should return a slice.
-	GetNested() interface{}
-	setNested(interface{})
-	generic() *Generic
+	// Zero or more (non-error) nested elements. These will be in
+	// namespaces managed by extensions.
+	GetNested() []interface{}
+	addNested(interface{})
 	innerxml() string
 }
 
@@ -113,8 +112,7 @@
 	Subject *Generic
 	Body *Generic
 	Thread *Generic
-	Any *Generic
-	Nested interface{}
+	Nested []interface{}
 }
 var _ xml.Marshaler = &Message{}
 var _ Stanza = &Message{}
@@ -131,8 +129,7 @@
 	Show *Generic
 	Status *Generic
 	Priority *Generic
-	Any *Generic
-	Nested interface{}
+	Nested []interface{}
 }
 var _ xml.Marshaler = &Presence{}
 var _ Stanza = &Presence{}
@@ -146,8 +143,7 @@
 	Lang string `xml:"attr"`
 	Innerxml string `xml:"innerxml"`
 	Error *Error
-	Any *Generic
-	Nested interface{}
+	Nested []interface{}
 }
 var _ xml.Marshaler = &Iq{}
 var _ Stanza = &Iq{}
@@ -335,10 +331,10 @@
 			return nil, err
 		}
 	}
-	if st.GetNested() != nil {
-		xml.Marshal(buf, st.GetNested())
-	} else if st.generic() != nil {
-		xml.Marshal(buf, st.generic())
+	if nested := st.GetNested() ; nested != nil {
+		for _, n := range(nested) {
+			xml.Marshal(buf, n)
+		}
 	} else if st.innerxml() != "" {
 		buf.WriteString(st.innerxml())
 	}
@@ -383,16 +379,12 @@
 	return m.Error
 }
 
-func (m *Message) GetNested() interface{} {
+func (m *Message) GetNested() []interface{} {
 	return m.Nested
 }
 
-func (m *Message) setNested(n interface{}) {
-	m.Nested = n
-}
-
-func (m *Message) generic() *Generic {
-	return m.Any
+func (m *Message) addNested(n interface{}) {
+	m.Nested = append(m.Nested, n)
 }
 
 func (m *Message) innerxml() string {
@@ -431,16 +423,12 @@
 	return p.Error
 }
 
-func (p *Presence) GetNested() interface{} {
+func (p *Presence) GetNested() []interface{} {
 	return p.Nested
 }
 
-func (p *Presence) setNested(n interface{}) {
-	p.Nested = n
-}
-
-func (p *Presence) generic() *Generic {
-	return p.Any
+func (p *Presence) addNested(n interface{}) {
+	p.Nested = append(p.Nested, n)
 }
 
 func (p *Presence) innerxml() string {
@@ -479,16 +467,12 @@
 	return iq.Error
 }
 
-func (iq *Iq) GetNested() interface{} {
+func (iq *Iq) GetNested() []interface{} {
 	return iq.Nested
 }
 
-func (iq *Iq) setNested(n interface{}) {
-	iq.Nested = n
-}
-
-func (iq *Iq) generic() *Generic {
-	return iq.Any
+func (iq *Iq) addNested(n interface{}) {
+	iq.Nested = append(iq.Nested, n)
 }
 
 func (iq *Iq) innerxml() string {
--- a/structs_test.go	Sat Jan 07 21:20:23 2012 -0700
+++ b/structs_test.go	Sat Jan 07 22:22:18 2012 -0700
@@ -83,8 +83,9 @@
 }
 
 func TestIqMarshal(t *testing.T) {
-	iq := &Iq{Type: "set", Id: "3", Any: &Generic{XMLName:
-			xml.Name{Space: NsBind, Local: "bind"}}}
+	iq := &Iq{Type: "set", Id: "3", Nested:
+		[]interface{}{Generic{XMLName: xml.Name{Space: NsBind,
+				Local: "bind"}}}}
 	exp := `<iq id="3" type="set"><bind xmlns="` + NsBind +
 		`"></bind></iq>`
 	assertMarshal(t, exp, iq)
@@ -106,11 +107,10 @@
 	if st.GetError() != nil {
 		t.Errorf("iq: error %v", st.GetError())
 	}
-	if st.generic() == nil {
-		t.Errorf("iq: nil child")
+	if st.innerxml() == "" {
+		t.Errorf("iq: empty child")
 	}
-	assertEquals(t, "foo", st.generic().XMLName.Local)
-	assertEquals(t, "text", st.generic().Chardata)
+	assertEquals(t, "<foo>text</foo>", st.innerxml())
 
 	str = `<message to="alice" from="bob"/>`
 	st, err = ParseStanza(str)
@@ -125,8 +125,8 @@
 	if st.GetError() != nil {
 		t.Errorf("message: error %v", st.GetError())
 	}
-	if st.generic() != nil {
-		t.Errorf("message: child %v", st.generic())
+	if st.innerxml() != "" {
+		t.Errorf("message: child %v", st.innerxml())
 	}
 
 	str = `<presence/>`
--- a/xmpp.go	Sat Jan 07 21:20:23 2012 -0700
+++ b/xmpp.go	Sat Jan 07 22:22:18 2012 -0700
@@ -301,9 +301,9 @@
 // Presence struct.  See RFC 3921, Section 3.
 func (cl *Client) StartSession(getRoster bool, pr *Presence) os.Error {
 	id := <- Id
-	iq := &Iq{To: cl.Jid.Domain, Id: id, Type: "set", Any:
-		&Generic{XMLName: xml.Name{Space: NsSession, Local:
-				"session"}}}
+	iq := &Iq{To: cl.Jid.Domain, Id: id, Type: "set", Nested:
+		[]interface{}{ Generic{XMLName: xml.Name{Space:
+					NsSession, Local: "session"}}}}
 	ch := make(chan os.Error)
 	f := func(st Stanza) bool {
 		if st.GetType() == "error" {