diff --git a/v2/soap/types/types.go b/v2/soap/types/types.go index cc3d585..4fb3ca0 100644 --- a/v2/soap/types/types.go +++ b/v2/soap/types/types.go @@ -29,24 +29,13 @@ type UI1 uint8 var _ SOAPValue = new(UI1) -// toStringNoError converts `v` to a string, returning empty string on error. -// This should only be used for String() implementations if no error can be -// returned by v.MarshalText(). -func toStringNoError(v encoding.TextMarshaler) string { - b, err := v.MarshalText() - if err != nil { - return "" - } - return string(b) -} - func NewUI1(v uint8) *UI1 { v2 := UI1(v) return &v2 } func (v *UI1) String() string { - return toStringNoError(v) + return strconv.FormatUint(uint64(*v), 10) } func (v *UI1) MarshalText() ([]byte, error) { @@ -69,7 +58,7 @@ func NewUI2(v uint16) *UI2 { } func (v *UI2) String() string { - return toStringNoError(v) + return strconv.FormatUint(uint64(*v), 10) } func (v *UI2) MarshalText() ([]byte, error) { @@ -92,7 +81,7 @@ func NewUI4(v uint32) *UI4 { } func (v *UI4) String() string { - return toStringNoError(v) + return strconv.FormatUint(uint64(*v), 10) } func (v *UI4) MarshalText() ([]byte, error) { @@ -115,7 +104,7 @@ func NewUI8(v uint64) *UI8 { } func (v *UI8) String() string { - return toStringNoError(v) + return strconv.FormatUint(uint64(*v), 10) } func (v *UI8) MarshalText() ([]byte, error) { @@ -138,7 +127,7 @@ func NewI1(v int8) *I1 { } func (v *I1) String() string { - return toStringNoError(v) + return strconv.FormatInt(int64(*v), 10) } func (v *I1) MarshalText() ([]byte, error) { @@ -161,7 +150,7 @@ func NewI2(v int16) *I2 { } func (v *I2) String() string { - return toStringNoError(v) + return strconv.FormatInt(int64(*v), 10) } func (v *I2) MarshalText() ([]byte, error) { @@ -184,7 +173,7 @@ func NewI4(v int32) *I4 { } func (v *I4) String() string { - return toStringNoError(v) + return strconv.FormatInt(int64(*v), 10) } func (v *I4) MarshalText() ([]byte, error) { @@ -207,7 +196,7 @@ func NewI8(v int64) *I8 { } func (v *I8) String() string { - return toStringNoError(v) + return strconv.FormatInt(int64(*v), 10) } func (v *I8) MarshalText() ([]byte, error) { @@ -230,11 +219,15 @@ func NewR4(v float32) *R4 { } func (v *R4) String() string { - return toStringNoError(v) + return string(v.marshalText(nil)) +} + +func (v *R4) marshalText(b []byte) []byte { + return strconv.AppendFloat(b, float64(*v), 'g', -1, 32) } func (v *R4) MarshalText() ([]byte, error) { - return strconv.AppendFloat(nil, float64(*v), 'g', -1, 32), nil + return v.marshalText(nil), nil } func (v *R4) UnmarshalText(b []byte) error { @@ -253,11 +246,15 @@ func NewR8(v float64) *R8 { } func (v *R8) String() string { - return toStringNoError(v) + return string(v.marshalText(nil)) +} + +func (v *R8) marshalText(b []byte) []byte { + return strconv.AppendFloat(nil, float64(*v), 'g', -1, 64) } func (v *R8) MarshalText() ([]byte, error) { - return strconv.AppendFloat(nil, float64(*v), 'g', -1, 64), nil + return v.marshalText(nil), nil } func (v *R8) UnmarshalText(b []byte) error { @@ -360,15 +357,22 @@ func (v Fixed14_4) Float64() float64 { } func (v *Fixed14_4) String() string { - return toStringNoError(v) + return string(v.marshalText(nil)) } -func (v *Fixed14_4) MarshalText() ([]byte, error) { +func (v *Fixed14_4) marshalText(b []byte) []byte { intPart, fracPart := v.Parts() if fracPart < 0 { fracPart = -fracPart } - return []byte(fmt.Sprintf("%d.%04d", intPart, fracPart)), nil + b = strconv.AppendInt(b, intPart, 10) + b = append(b, '.') + b = appendInt(b, int64(fracPart), 4) + return b +} + +func (v *Fixed14_4) MarshalText() ([]byte, error) { + return v.marshalText(nil), nil } var decimalByte = []byte{'.'} @@ -482,6 +486,32 @@ func parseInt(b []byte, err *error) int { return int(v) } +var zeroDigits []byte = []byte("000") + +// appendInt appends `n` in decimal to `b`, with up to 3 digits of +// zero-padding to fill to a minimum of 4 digits. +func appendInt(b []byte, n int64, padding int) []byte { + if n > -1000 && n < 1000 { + if n < 0 { + n = -n + b = append(b, '-') + } + var digits int + if n < 10 { + digits = 1 + } else if n < 100 { + digits = 2 + } else if n < 1000 { + digits = 3 + } + numZeros := padding - digits + if numZeros > 0 { + b = append(b, zeroDigits[:numZeros]...) + } + } + return strconv.AppendInt(b, n, 10) +} + var dateRegexps = []*regexp.Regexp{ // yyyy[-mm[-dd]] regexp.MustCompile(`^(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?$`), @@ -551,7 +581,7 @@ func (tod TimeOfDay) ToDuration() time.Duration { } func (tod *TimeOfDay) String() string { - return toStringNoError(tod) + return string(tod.marshalText(nil)) } // IsValid returns true iff v is positive and <= 24 hours. @@ -573,11 +603,20 @@ func (tod *TimeOfDay) clear() { tod.Second = 0 } +func (tod *TimeOfDay) marshalText(b []byte) []byte { + b = appendInt(b, int64(tod.Hour), 2) + b = append(b, ':') + b = appendInt(b, int64(tod.Minute), 2) + b = append(b, ':') + b = appendInt(b, int64(tod.Second), 2) + return b +} + func (tod *TimeOfDay) MarshalText() ([]byte, error) { if err := tod.CheckValid(); err != nil { return nil, err } - return []byte(fmt.Sprintf("%02d:%02d:%02d", tod.Hour, tod.Minute, tod.Second)), nil + return tod.marshalText(nil), nil } var timeRegexps = []*regexp.Regexp{ @@ -623,7 +662,7 @@ type TimeOfDayTZ struct { var _ SOAPValue = &TimeOfDayTZ{} func (todz *TimeOfDayTZ) String() string { - return toStringNoError(todz) + return string(todz.TZ.marshalText(nil)) } // clear removes data from v, setting to default values. @@ -633,12 +672,9 @@ func (todz *TimeOfDayTZ) clear() { } func (todz *TimeOfDayTZ) MarshalText() ([]byte, error) { - result, err := todz.TimeOfDay.MarshalText() - if err != nil { - return nil, err - } - result = append(result, []byte(todz.TZ.String())...) - return result, nil + b := todz.TimeOfDay.marshalText(nil) + b = todz.TZ.marshalText(b) + return b, nil } func (todz *TimeOfDayTZ) UnmarshalText(b []byte) error { @@ -679,7 +715,7 @@ func (d Date) ToTime(loc *time.Location) time.Time { } func (d *Date) String() string { - return toStringNoError(d) + return string(d.marshalText(nil)) } // CheckValid returns an error if the date components are out of range. @@ -698,8 +734,17 @@ func (d *Date) clear() { d.Day = 0 } +func (d *Date) marshalText(b []byte) []byte { + b = appendInt(b, int64(d.Year), 2) + b = append(b, '-') + b = appendInt(b, int64(d.Month), 2) + b = append(b, '-') + b = appendInt(b, int64(d.Day), 2) + return b +} + func (d *Date) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day)), nil + return d.marshalText(nil), nil } func (d *Date) UnmarshalText(b []byte) error { @@ -749,7 +794,7 @@ func DateTimeFromTime(v time.Time) DateTime { } func (dt *DateTime) String() string { - return toStringNoError(dt) + return string(dt.marshalText(nil)) } func (dt DateTime) ToTime(loc *time.Location) time.Time { @@ -764,20 +809,15 @@ func (dt *DateTime) clear() { dt.TimeOfDay.clear() } +func (dt *DateTime) marshalText(b []byte) []byte { + b = dt.Date.marshalText(b) + b = append(b, 'T') + b = dt.TimeOfDay.marshalText(b) + return b +} + func (dt *DateTime) MarshalText() ([]byte, error) { - var result []byte - d, err := dt.Date.MarshalText() - if err != nil { - return nil, err - } - t, err := dt.TimeOfDay.MarshalText() - if err != nil { - return nil, err - } - result = append(result, d...) - result = append(result, 'T') - result = append(result, t...) - return result, nil + return dt.marshalText(nil), nil } func (dt *DateTime) UnmarshalText(b []byte) error { @@ -816,7 +856,7 @@ func DateTimeTZFromTime(t time.Time) DateTimeTZ { } func (dtz *DateTimeTZ) String() string { - return toStringNoError(dtz) + return string(dtz.marshalText(nil)) } // Time converts `dtz` to time.Time, using defaultLoc as the default location if @@ -841,21 +881,16 @@ func (dtz *DateTimeTZ) clear() { dtz.TZ.clear() } +func (dtz *DateTimeTZ) marshalText(b []byte) []byte { + b = dtz.Date.marshalText(b) + b = append(b, 'T') + b = dtz.TimeOfDay.marshalText(b) + b = dtz.TZ.marshalText(b) + return b +} + func (dtz *DateTimeTZ) MarshalText() ([]byte, error) { - var result []byte - d, err := dtz.Date.MarshalText() - if err != nil { - return nil, err - } - t, err := dtz.TimeOfDay.MarshalText() - if err != nil { - return nil, err - } - result = append(result, d...) - result = append(result, 'T') - result = append(result, t...) - result = append(result, []byte(dtz.TZ.String())...) - return result, nil + return dtz.marshalText(nil), nil } func (dtz *DateTimeTZ) UnmarshalText(b []byte) error { @@ -921,23 +956,8 @@ func (tzd TZD) Location(defaultLoc *time.Location) *time.Location { return time.FixedZone(tzd.String(), tzd.Offset) } -func (tzd TZD) String() string { - if !tzd.HasTZ { - return "" - } - - if tzd.Offset == 0 { - return "Z" - } - - offsetMins := tzd.Offset / 60 - sign := '+' - if offsetMins < 1 { - offsetMins = -offsetMins - sign = '-' - } - h, m := offsetMins/60, offsetMins%60 - return fmt.Sprintf("%c%02d:%02d", sign, h, m) +func (tzd *TZD) String() string { + return string(tzd.marshalText(nil)) } // clear removes offset information from `v`. @@ -946,6 +966,30 @@ func (tzd *TZD) clear() { tzd.HasTZ = false } +func (tzd *TZD) marshalText(b []byte) []byte { + if !tzd.HasTZ { + return b + } + + if tzd.Offset == 0 { + b = append(b, 'Z') + return b + } + + offsetMins := tzd.Offset / 60 + var sign byte = '+' + if offsetMins < 1 { + offsetMins = -offsetMins + sign = '-' + } + h, m := offsetMins/60, offsetMins%60 + b = append(b, sign) + b = appendInt(b, int64(h), 2) + b = append(b, ':') + b = appendInt(b, int64(m), 2) + return b +} + // (+|-)(hh):(mm) var timezoneRegexp = regexp.MustCompile(`^([+-])(\d{2}):(\d{2})$`)