From 357d965c922fd6935ccba897b2b6abd0681e765a Mon Sep 17 00:00:00 2001 From: benoitkugler Date: Wed, 3 Apr 2019 12:26:41 +0200 Subject: [PATCH 1/2] Refactoring the decoding --- xmlrpc.go | 212 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 131 insertions(+), 81 deletions(-) diff --git a/xmlrpc.go b/xmlrpc.go index b4c0fd0..d380a0b 100644 --- a/xmlrpc.go +++ b/xmlrpc.go @@ -15,16 +15,23 @@ import ( "time" ) +// Array is a generic array type Array []interface{} + +// Struct is a generic map type Struct map[string]interface{} -var xmlSpecial = map[byte]string{ - '<': "<", - '>': ">", - '"': """, - '\'': "'", - '&': "&", -} +var ( + xmlSpecial = map[byte]string{ + '<': "<", + '>': ">", + '"': """, + '\'': "'", + '&': "&", + } + errStartingTagNotFound = errors.New("starting tag expexted") + errEndingTagNotFound = errors.New("ending tag expexted") +) func xmlEscape(s string) string { var b bytes.Buffer @@ -39,29 +46,22 @@ func xmlEscape(s string) string { return b.String() } -type valueNode struct { - Type string `xml:"attr"` - Body string `xml:"chardata"` -} - -func next(p *xml.Decoder) (xml.Name, interface{}, error) { +func next(p *xml.Decoder) (interface{}, error) { se, e := nextStart(p) if e != nil { - return xml.Name{}, nil, e + return nil, e } - - var nv interface{} switch se.Name.Local { case "string": var s string if e = p.DecodeElement(&s, &se); e != nil { - return xml.Name{}, nil, e + return nil, e } - return xml.Name{}, s, nil + return s, nil case "boolean": var s string if e = p.DecodeElement(&s, &se); e != nil { - return xml.Name{}, nil, e + return nil, e } s = strings.TrimSpace(s) var b bool @@ -73,27 +73,27 @@ func next(p *xml.Decoder) (xml.Name, interface{}, error) { default: e = errors.New("invalid boolean value") } - return xml.Name{}, b, e + return b, e case "int", "i1", "i2", "i4", "i8": var s string var i int if e = p.DecodeElement(&s, &se); e != nil { - return xml.Name{}, nil, e + return nil, e } i, e = strconv.Atoi(strings.TrimSpace(s)) - return xml.Name{}, i, e + return i, e case "double": var s string var f float64 if e = p.DecodeElement(&s, &se); e != nil { - return xml.Name{}, nil, e + return nil, e } f, e = strconv.ParseFloat(strings.TrimSpace(s), 64) - return xml.Name{}, f, e + return f, e case "dateTime.iso8601": var s string if e = p.DecodeElement(&s, &se); e != nil { - return xml.Name{}, nil, e + return nil, e } t, e := time.Parse("20060102T15:04:05", s) if e != nil { @@ -102,89 +102,123 @@ func next(p *xml.Decoder) (xml.Name, interface{}, error) { t, e = time.Parse("2006-01-02T15:04:05", s) } } - return xml.Name{}, t, e + return t, e case "base64": var s string if e = p.DecodeElement(&s, &se); e != nil { - return xml.Name{}, nil, e + return nil, e } - if b, e := base64.StdEncoding.DecodeString(s); e != nil { - return xml.Name{}, nil, e - } else { - return xml.Name{}, b, nil + b, e := base64.StdEncoding.DecodeString(s) + if e != nil { + return nil, e } - case "member": - nextStart(p) - return next(p) - case "value": - nextStart(p) - return next(p) - case "name": - nextStart(p) - return next(p) + return b, nil case "struct": st := Struct{} - se, e = nextStart(p) - for e == nil && se.Name.Local == "member" { - // name - se, e = nextStart(p) - if se.Name.Local != "name" { - return xml.Name{}, nil, errors.New("invalid response") - } - if e != nil { + for { + se, err := nextStart(p) + if err == errStartingTagNotFound { // end of struct break + } else if err != nil { + return nil, err + } else if se.Name.Local != "member" { + return nil, errors.New("member element expected") + } + se, err = nextStart(p) + if err != nil { + return nil, err + } else if se.Name.Local != "name" { + return nil, errors.New("name element expected") } var name string - if e = p.DecodeElement(&name, &se); e != nil { - return xml.Name{}, nil, e + if e = p.DecodeElement(&name, &se); e != nil { // DecodeElement closes name element + return nil, e } - se, e = nextStart(p) - if e != nil { - break + se, err = nextStart(p) + if err != nil { + return nil, err + } else if se.Name.Local != "value" { + return nil, errors.New("value element for member expected") } - // value - _, value, e := next(p) - if se.Name.Local != "value" { - return xml.Name{}, nil, errors.New("invalid response") - } + value, e := next(p) if e != nil { - break + return nil, e } - st[name] = value - - se, e = nextStart(p) - if e != nil { - break + err = nextEnd(p) // value end + if err != nil { + return nil, err + } + err = nextEnd(p) // member end + if err != nil { + return nil, err } + st[name] = value } - return xml.Name{}, st, nil + return st, nil case "array": + fmt.Println("reading array") + se, err := nextStart(p) // data + if err != nil { + return nil, err + } else if se.Name.Local != "data" { + return nil, errors.New("data element expected") + } var ar Array - nextStart(p) // data - nextStart(p) // top of value for { - _, value, e := next(p) - if e != nil { + se, err := nextStart(p) // value + if err == errStartingTagNotFound { // end of array, end data reached break + } else if err != nil { + return nil, err + } else if se.Name.Local != "value" { + return nil, errors.New("value element expected") + } + value, e := next(p) + if e != nil { + return nil, e + } + err = nextEnd(p) // closing value + if err != nil { + return nil, err } + ar = append(ar, value) - if reflect.ValueOf(value).Kind() != reflect.Map { - nextStart(p) - } } - return xml.Name{}, ar, nil + err = nextEnd(p) // closing array + if err != nil { + return nil, err + } + return ar, nil case "nil": - return xml.Name{}, nil, nil - } - - if e = p.DecodeElement(nv, &se); e != nil { - return xml.Name{}, nil, e + return nil, nil + default: + var nv interface{} + if e = p.DecodeElement(nv, &se); e != nil { + return nil, e + } + return nv, e } - return se.Name, nv, e } + +// func nextStart(p *xml.Decoder) (xml.StartElement, error) { +// for { +// t, e := p.Token() +// if e != nil { +// return xml.StartElement{}, e +// } +// switch t := t.(type) { +// case xml.StartElement: +// fmt.Println("at ", t.Name.Local) +// return t, nil +// case xml.EndElement: +// fmt.Println("closing ", t.Name.Local) +// } +// } +// } + func nextStart(p *xml.Decoder) (xml.StartElement, error) { for { t, e := p.Token() @@ -194,9 +228,25 @@ func nextStart(p *xml.Decoder) (xml.StartElement, error) { switch t := t.(type) { case xml.StartElement: return t, nil + case xml.EndElement: + return xml.StartElement{}, errStartingTagNotFound + } + } +} + +func nextEnd(p *xml.Decoder) error { + for { + t, e := p.Token() + if e != nil { + return e + } + switch t.(type) { + case xml.EndElement: + return nil + case xml.StartElement: + return errEndingTagNotFound } } - panic("unreachable") } func toXml(v interface{}, typ bool) (s string) { @@ -350,7 +400,7 @@ func call(client *http.Client, url, name string, args ...interface{}) (v interfa if se.Name.Local != "value" { return nil, errors.New("invalid response: missing value") } - _, v, e = next(p) + v, e = next(p) return v, e } From 08c582c2d69acd8c48a4880b348bcf38f3f8aef0 Mon Sep 17 00:00:00 2001 From: benoitkugler Date: Thu, 4 Apr 2019 10:16:24 +0200 Subject: [PATCH 2/2] added tests --- xmlrpc.go | 17 ---- xmlrpc_test.go | 268 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 267 insertions(+), 18 deletions(-) diff --git a/xmlrpc.go b/xmlrpc.go index d380a0b..d703b94 100644 --- a/xmlrpc.go +++ b/xmlrpc.go @@ -158,7 +158,6 @@ func next(p *xml.Decoder) (interface{}, error) { } return st, nil case "array": - fmt.Println("reading array") se, err := nextStart(p) // data if err != nil { return nil, err @@ -203,22 +202,6 @@ func next(p *xml.Decoder) (interface{}, error) { } } -// func nextStart(p *xml.Decoder) (xml.StartElement, error) { -// for { -// t, e := p.Token() -// if e != nil { -// return xml.StartElement{}, e -// } -// switch t := t.(type) { -// case xml.StartElement: -// fmt.Println("at ", t.Name.Local) -// return t, nil -// case xml.EndElement: -// fmt.Println("closing ", t.Name.Local) -// } -// } -// } - func nextStart(p *xml.Decoder) (xml.StartElement, error) { for { t, e := p.Token() diff --git a/xmlrpc_test.go b/xmlrpc_test.go index 22e482f..8478a98 100644 --- a/xmlrpc_test.go +++ b/xmlrpc_test.go @@ -55,7 +55,7 @@ func createServer(path, name string, f func(args ...interface{}) (interface{}, e http.Error(w, "missing value", http.StatusBadRequest) return } - _, v, err := next(p) + v, err := next(p) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return @@ -349,3 +349,269 @@ func TestParseMixedArray(t *testing.T) { t.Fatal("expected array with 4 entries") } } + +type ParseNestedArray struct { +} + +func (h *ParseNestedArray) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(` + + + + + + + + + + + + type + folder + + folderid + QVlJS3ZXTjGu4lczs4ugVw + + label + SEJOURS + + modificationdate + 0 + + createddate + 1554311194000 + + + + type + folder + + folderid + sdsd + + label + ETE + + modificationdate + 0 + + createddate + 1554221058000 + + + + + + + + + + + + albumid + QVlJS3ZXTjEpIPrMXDKLuw + + label + PHOTOS + + orderby + date + + ordertype + ascending + + date + 1554221064000 + + createddate + 1554221068000 + + lastvisitdate + 1554221068000 + + lastfileaddeddate + 0 + + public + 0 + + allowdownload + 1 + + allowupload + 0 + + allowprintorder + 1 + + allowsendcomments + 1 + + folderid + zezeze + + + + albumid + zeeze + + label + test + + orderby + date + + ordertype + ascending + + date + 1554311214000 + + createddate + 1554311217000 + + lastvisitdate + 1554311241000 + + lastfileaddeddate + 1554311238000 + + public + 0 + + allowdownload + 1 + + allowupload + 0 + + allowprintorder + 1 + + allowsendcomments + 1 + + folderid + QVlJS3ZXTjGM3tkbWu_Z0A + + + + + + + + + + + + contactid + QVlJS3ZXzeeeTjH9d-QUwXR5jg + + login + benoit.zez + + type + 0 + + usePreferences + 0 + + password + ddere + + firstname + + + lastname + + + email + zelkzjez@test.fr + + phoneNumber + + + + + contactid + zezez + + login + zezezeze + + type + 0 + + usePreferences + 0 + + password + dezzrere + + firstname + a + + lastname + a + + email + xxxxx@gmail.com + + phoneNumber + + + + + contactid + QVlJS3ZXTjddFSflbF5i2KMQ + + login + eee + + type + 0 + + usePreferences + 0 + + password + eeeee + + firstname + Benoit + + lastname + KUG + + email + sderedr@free.fr + + phoneNumber + + + + + + + + + + + + `)) +} + +func TestParseNestedArray(t *testing.T) { + ts := httptest.NewServer(&ParseNestedArray{}) + defer ts.Close() + + res, err := NewClient(ts.URL + "/").Call("Irrelevant") + + if err != nil { + t.Fatal(err) + } + + if len(res.(Array)) != 3 { + t.Fatal("expected array with 3 entries") + } +}