diff --git a/soap/soap_test.go b/soap/soap_test.go index 0115773..b436df5 100644 --- a/soap/soap_test.go +++ b/soap/soap_test.go @@ -369,6 +369,10 @@ func TestXsdDateTime(t *testing.T) { XMLName xml.Name `xml:"TestDateTime"` Datetime XSDDateTime } + type TestAttrDateTime struct { + XMLName xml.Name `xml:"TestAttrDateTime"` + Datetime XSDDateTime `xml:"Datetime,attr"` + } // test marshalling { // without nanosecond @@ -433,6 +437,22 @@ func TestXsdDateTime(t *testing.T) { } } + // test marshalling as attribute + { + testDateTime := TestAttrDateTime{ + Datetime: CreateXsdDateTime(time.Date(1951, time.October, 22, 1, 2, 3, 4, time.UTC), true), + } + if output, err := xml.MarshalIndent(testDateTime, "", ""); err != nil { + t.Error(err) + } else { + outputstr := string(output) + expected := "" + if outputstr != expected { + t.Errorf("Got: %v\nExpected: %v", outputstr, expected) + } + } + } + // test unmarshalling { dateTimes := map[string]time.Time{ @@ -451,6 +471,25 @@ func TestXsdDateTime(t *testing.T) { } } } + + // test unmarshalling as attribute + { + dateTimes := map[string]time.Time{ + "": time.Date(1951, time.October, 22, 1, 2, 3, 4, time.FixedZone("-0800", -8*60*60)), + "": time.Date(1951, time.October, 22, 1, 2, 3, 0, time.UTC), + "": time.Date(1951, time.October, 22, 1, 2, 3, 0, time.Local), + } + for dateTimeStr, dateTimeObj := range dateTimes { + parsedDt := TestAttrDateTime{} + if err := xml.Unmarshal([]byte(dateTimeStr), &parsedDt); err != nil { + t.Error(err) + } else { + if !parsedDt.Datetime.ToGoTime().Equal(dateTimeObj) { + t.Errorf("Got: %#v\nExpected: %#v", parsedDt.Datetime.ToGoTime(), dateTimeObj) + } + } + } + } } // TestXsdDateTime checks the marshalled xsd datetime @@ -459,6 +498,10 @@ func TestXsdDate(t *testing.T) { XMLName xml.Name `xml:"TestDate"` Date XSDDate } + type TestAttrDate struct { + XMLName xml.Name `xml:"TestAttrDate"` + Date XSDDate `xml:"Date,attr"` + } // test marshalling { @@ -508,6 +551,22 @@ func TestXsdDate(t *testing.T) { } } + // test marshalling as attribute + { + testDate := TestAttrDate{ + Date: CreateXsdDate(time.Date(1951, time.October, 22, 0, 0, 0, 0, time.UTC), true), + } + if output, err := xml.MarshalIndent(testDate, "", ""); err != nil { + t.Error(err) + } else { + outputstr := string(output) + expected := "" + if outputstr != expected { + t.Errorf("Got: %v\nExpected: %v", outputstr, expected) + } + } + } + // test unmarshalling { dates := map[string]time.Time{ @@ -526,6 +585,25 @@ func TestXsdDate(t *testing.T) { } } } + + // test unmarshalling as attribute + { + dates := map[string]time.Time{ + "": time.Date(1951, time.October, 22, 0, 0, 0, 0, time.Local), + "": time.Date(1951, time.October, 22, 0, 0, 0, 0, time.UTC), + "": time.Date(1951, time.October, 22, 0, 0, 0, 0, time.FixedZone("UTC-8", -8*60*60)), + } + for dateStr, dateObj := range dates { + parsedDate := TestAttrDate{} + if err := xml.Unmarshal([]byte(dateStr), &parsedDate); err != nil { + t.Error(dateStr, err) + } else { + if !parsedDate.Date.ToGoTime().Equal(dateObj) { + t.Errorf("Got: %#v\nExpected: %#v", parsedDate.Date.ToGoTime(), dateObj) + } + } + } + } } // TestXsdTime checks the marshalled xsd datetime @@ -534,6 +612,10 @@ func TestXsdTime(t *testing.T) { XMLName xml.Name `xml:"TestTime"` Time XSDTime } + type TestAttrTime struct { + XMLName xml.Name `xml:"TestAttrTime"` + Time XSDTime `xml:"Time,attr"` + } // test marshalling { @@ -578,6 +660,21 @@ func TestXsdTime(t *testing.T) { } } } + // test marshalling as attribute + { + testTime := TestAttrTime{ + Time: CreateXsdTime(12, 13, 14, 4, time.FixedZone("Test", -19800)), + } + if output, err := xml.MarshalIndent(testTime, "", ""); err != nil { + t.Error(err) + } else { + outputstr := string(output) + expected := "" + if outputstr != expected { + t.Errorf("Got: %v\nExpected: %v", outputstr, expected) + } + } + } // test unmarshalling without TZ { @@ -652,6 +749,30 @@ func TestXsdTime(t *testing.T) { } } } + // test unmarshalling as attribute + { + timeStr := "" + parsedTime := TestAttrTime{} + if err := xml.Unmarshal([]byte(timeStr), &parsedTime); err != nil { + t.Error(err) + } else { + if parsedTime.Time.Hour() != 12 { + t.Errorf("Got hour %#v\nExpected: %#v", parsedTime.Time.Hour(), 12) + } + if parsedTime.Time.Minute() != 13 { + t.Errorf("Got minute %#v\nExpected: %#v", parsedTime.Time.Minute(), 13) + } + if parsedTime.Time.Second() != 14 { + t.Errorf("Got second %#v\nExpected: %#v", parsedTime.Time.Second(), 14) + } + if parsedTime.Time.Nanosecond() != 0 { + t.Errorf("Got nsec %#v\nExpected: %#v", parsedTime.Time.Nanosecond(), 0) + } + if parsedTime.Time.Location().String() != "UTC" { + t.Errorf("Got location %v\nExpected: UTC", parsedTime.Time.Location().String()) + } + } + } } func TestHTTPError(t *testing.T) { diff --git a/soap/xsdDateTime.go b/soap/xsdDateTime.go index f297daa..375bdae 100644 --- a/soap/xsdDateTime.go +++ b/soap/xsdDateTime.go @@ -41,8 +41,28 @@ func (xdt *XSDDateTime) ToGoTime() time.Time { xdt.innerTime.Nanosecond(), time.Local) } -// MarshalXML implementation on DateTime to skip "zero" time values. It also checks if nanoseconds and TZ exist. +// MarshalXML implements xml.MarshalerAttr on XSDDateTime func (xdt XSDDateTime) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + xdtString := xdt.string() + if xdtString != "" { + return e.EncodeElement(xdtString, start) + } + return nil +} + +// MarshalXMLAttr implements xml.MarshalerAttr on XSDDateTime +func (xdt XSDDateTime) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { + xdtString := xdt.string() + attr := xml.Attr{} + if xdtString != "" { + attr.Name = name + attr.Value = xdtString + } + return attr, nil +} + +// returns string representation and skips "zero" time values. It also checks if nanoseconds and TZ exist. +func (xdt XSDDateTime) string() string { if !xdt.innerTime.IsZero() { dateTimeLayout := time.RFC3339Nano if xdt.innerTime.Nanosecond() == 0 { @@ -57,33 +77,31 @@ func (xdt XSDDateTime) MarshalXML(e *xml.Encoder, start xml.StartElement) error toks = strings.SplitN(toks[0], "-", 2) dtString = dateAndTime[0] + "T" + toks[0] } - e.EncodeElement(dtString, start) + return dtString } - return nil + return "" } -// UnmarshalXML implementation on DateTimeg to use dateTimeLayout +// UnmarshalXML implements xml.Unmarshaler on XSDDateTime to use time.RFC3339Nano func (xdt *XSDDateTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { - var err error - xdt.innerTime, xdt.hasTz, err = unmarshalTime(d, start, time.RFC3339Nano) + var content string + err := d.DecodeElement(&content, &start) + if err != nil { + return err + } + xdt.innerTime, xdt.hasTz, err = fromString(content, time.RFC3339Nano) return err } -// CreateXsdDateTime creates an object represent xsd:datetime object in Golang -func CreateXsdDateTime(dt time.Time, hasTz bool) XSDDateTime { - return XSDDateTime{ - innerTime: dt, - hasTz: hasTz, - } +// UnmarshalXMLAttr implements xml.UnmarshalerAttr on XSDDateTime to use time.RFC3339Nano +func (xdt *XSDDateTime) UnmarshalXMLAttr(attr xml.Attr) error { + var err error + xdt.innerTime, xdt.hasTz, err = fromString(attr.Value, time.RFC3339Nano) + return err } -func unmarshalTime(d *xml.Decoder, start xml.StartElement, format string) (time.Time, bool, error) { +func fromString(content string, format string) (time.Time, bool, error) { var t time.Time - var content string - err := d.DecodeElement(&content, &start) - if err != nil { - return t, true, err - } if content == "" { return t, true, nil } @@ -114,8 +132,16 @@ func unmarshalTime(d *xml.Decoder, start xml.StartElement, format string) (time. content += "Z" } } - t, err = time.Parse(format, content) - return t, hasTz, nil + t, err := time.Parse(format, content) + return t, hasTz, err +} + +// CreateXsdDateTime creates an object represent xsd:datetime object in Golang +func CreateXsdDateTime(dt time.Time, hasTz bool) XSDDateTime { + return XSDDateTime{ + innerTime: dt, + hasTz: hasTz, + } } // XSDDate is a type for representing xsd:date in Golang @@ -139,8 +165,28 @@ func (xd *XSDDate) ToGoTime() time.Time { 0, 0, 0, 0, time.Local) } -// MarshalXML implementation on DateTimeg to skip "zero" time values +// MarshalXML implementation on XSDDate func (xd XSDDate) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + xdtString := xd.string() + if xdtString != "" { + return e.EncodeElement(xdtString, start) + } + return nil +} + +// MarshalXMLAttr implementation on XSDDate +func (xd XSDDate) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { + xdString := xd.string() + attr := xml.Attr{} + if xdString != "" { + attr.Name = name + attr.Value = xdString + } + return attr, nil +} + +// returns string representation and skips "zero" time values +func (xd XSDDate) string() string { if !xd.innerDate.IsZero() { dateString := xd.innerDate.Format(dateLayout) // serialize with TZ if !xd.hasTz { @@ -156,15 +202,26 @@ func (xd XSDDate) MarshalXML(e *xml.Encoder, start xml.StartElement) error { } } } - e.EncodeElement(dateString, start) + return dateString } - return nil + return "" } -// UnmarshalXML implementation on DateTimeg to use dateTimeLayout +// UnmarshalXML implements xml.Unmarshaler on XSDDate to use dateLayout func (xd *XSDDate) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var content string + err := d.DecodeElement(&content, &start) + if err != nil { + return err + } + xd.innerDate, xd.hasTz, err = fromString(content, dateLayout) + return err +} + +// UnmarshalXMLAttr implements xml.UnmarshalerAttr on XSDDate to use dateLayout +func (xd *XSDDate) UnmarshalXMLAttr(attr xml.Attr) error { var err error - xd.innerDate, xd.hasTz, err = unmarshalTime(d, start, dateLayout) + xd.innerDate, xd.hasTz, err = fromString(attr.Value, dateLayout) return err } @@ -182,8 +239,28 @@ type XSDTime struct { hasTz bool } -// MarshalXML implementation on DateTimeg to skip "zero" time values +// MarshalXML implements xml.Marshaler on XSDTime func (xt XSDTime) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + xdtString := xt.string() + if xdtString != "" { + return e.EncodeElement(xdtString, start) + } + return nil +} + +// MarshalXMLAttr implements xml.MarshalerAttr on XSDTime +func (xt XSDTime) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { + xdString := xt.string() + attr := xml.Attr{} + if xdString != "" { + attr.Name = name + attr.Value = xdString + } + return attr, nil +} + +// returns string representation and skips "zero" time values +func (xt XSDTime) string() string { if !xt.innerTime.IsZero() { dateTimeLayout := time.RFC3339Nano if xt.innerTime.Nanosecond() == 0 { @@ -198,20 +275,30 @@ func (xt XSDTime) MarshalXML(e *xml.Encoder, start xml.StartElement) error { toks = strings.SplitN(toks[0], "-", 2) timeString = toks[0] } - e.EncodeElement(timeString, start) + return timeString } - return nil + return "" } -// UnmarshalXML implementation on DateTimeg to use dateTimeLayout +// UnmarshalXML implements xml.Unmarshaler on XSDTime to use dateTimeLayout func (xt *XSDTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { - var t time.Time var err error var content string err = d.DecodeElement(&content, &start) if err != nil { return err } + return xt.fromString(content) +} + +// UnmarshalXMLAttr implements xml.UnmarshalerAttr on XSDTime to use dateTimeLayout +func (xt *XSDTime) UnmarshalXMLAttr(attr xml.Attr) error { + return xt.fromString(attr.Value) +} + +func (xt *XSDTime) fromString(content string) error { + var t time.Time + var err error if content == "" { xt.innerTime = t return nil