This repository has been archived by the owner on Jul 13, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 22
/
unmarshal.go
356 lines (297 loc) · 11.2 KB
/
unmarshal.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
package icalendar
import (
"fmt"
"github.com/taviti/caldav-go/icalendar/properties"
"github.com/taviti/caldav-go/utils"
"log"
"reflect"
"regexp"
"strconv"
"strings"
)
var _ = log.Print
var splitter = regexp.MustCompile("\r?\n")
type token struct {
name string
components map[string][]*token
properties map[properties.PropertyName][]*properties.Property
}
func tokenize(encoded string) (*token, error) {
if encoded = strings.TrimSpace(encoded); encoded == "" {
return nil, utils.NewError(tokenize, "no content to tokenize", encoded, nil)
}
return tokenizeSlice(splitter.Split(encoded, -1))
}
func tokenizeSlice(slice []string, name ...string) (*token, error) {
tok := new(token)
size := len(slice)
if len(name) > 0 {
tok.name = name[0]
} else if size <= 0 {
return nil, utils.NewError(tokenizeSlice, "token has no content", slice, nil)
}
tok.properties = make(map[properties.PropertyName][]*properties.Property, 0)
tok.components = make(map[string][]*token, 0)
for i := 0; i < size; i++ {
line := slice[i]
prop := properties.UnmarshalProperty(line)
if prop.Name.Equals("begin") {
for j := i; j < size; j++ {
end := strings.Replace(line, "BEGIN", "END", 1)
if slice[j] == end {
if component, err := tokenizeSlice(slice[i+1:j], prop.Value); err != nil {
msg := fmt.Sprintf("unable to tokenize %s component", prop.Value)
return nil, utils.NewError(tokenizeSlice, msg, slice, err)
} else {
existing, _ := tok.components[prop.Value]
tok.components[prop.Value] = append(existing, component)
i = j
break
}
}
}
} else if existing, ok := tok.properties[prop.Name]; ok {
tok.properties[prop.Name] = []*properties.Property{prop}
} else {
tok.properties[prop.Name] = append(existing, prop)
}
}
return tok, nil
}
func hydrateInterface(v reflect.Value, prop *properties.Property) (bool, error) {
// unable to decode into empty values
if isInvalidOrEmptyValue(v) {
return false, nil
}
var i = v.Interface()
var hasValue = false
// decode a value if possible
if decoder, ok := i.(properties.CanDecodeValue); ok {
if err := decoder.DecodeICalValue(prop.Value); err != nil {
return false, utils.NewError(hydrateInterface, "error decoding property value", v, err)
} else {
hasValue = true
}
}
// decode any params, if supported
if len(prop.Params) > 0 {
if decoder, ok := i.(properties.CanDecodeParams); ok {
if err := decoder.DecodeICalParams(prop.Params); err != nil {
return false, utils.NewError(hydrateInterface, "error decoding property parameters", v, err)
}
}
}
// finish with any validation
if validator, ok := i.(properties.CanValidateValue); ok {
if err := validator.ValidateICalValue(); err != nil {
return false, utils.NewError(hydrateInterface, "error validating property value", v, err)
}
}
return hasValue, nil
}
func hydrateLiteral(v reflect.Value, prop *properties.Property) (reflect.Value, error) {
literal := dereferencePointerValue(v)
switch literal.Kind() {
case reflect.Bool:
if i, err := strconv.ParseBool(prop.Value); err != nil {
return literal, utils.NewError(hydrateLiteral, "unable to decode bool "+prop.Value, literal.Interface(), err)
} else {
literal.SetBool(i)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if i, err := strconv.ParseInt(prop.Value, 10, 64); err != nil {
return literal, utils.NewError(hydrateLiteral, "unable to decode int "+prop.Value, literal.Interface(), err)
} else {
literal.SetInt(i)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
if i, err := strconv.ParseUint(prop.Value, 10, 64); err != nil {
return literal, utils.NewError(hydrateLiteral, "unable to decode uint "+prop.Value, literal.Interface(), err)
} else {
literal.SetUint(i)
}
case reflect.Float32, reflect.Float64:
if i, err := strconv.ParseFloat(prop.Value, 64); err != nil {
return literal, utils.NewError(hydrateLiteral, "unable to decode float "+prop.Value, literal.Interface(), err)
} else {
literal.SetFloat(i)
}
case reflect.String:
literal.SetString(prop.Value)
default:
return literal, utils.NewError(hydrateLiteral, "unable to decode value as literal "+prop.Value, literal.Interface(), nil)
}
return literal, nil
}
func hydrateProperty(v reflect.Value, prop *properties.Property) error {
// check to see if the interface handles it's own hydration
if handled, err := hydrateInterface(v, prop); err != nil {
return utils.NewError(hydrateProperty, "unable to hydrate interface", v, err)
} else if handled {
return nil // exit early if handled by the interface
}
// if we got here, we need to create a new instance to
// set into the property.
var vnew, varr = newValue(v)
var vlit bool
// check to see if the new value handles it's own hydration
if handled, err := hydrateInterface(vnew, prop); err != nil {
return utils.NewError(hydrateProperty, "unable to hydrate new interface value", vnew, err)
} else if vlit = !handled; vlit {
// if not, treat it as a literal
if vnewlit, err := hydrateLiteral(vnew, prop); err != nil {
return utils.NewError(hydrateProperty, "unable to hydrate new literal value", vnew, err)
} else if _, err := hydrateInterface(vnewlit, prop); err != nil {
return utils.NewError(hydrateProperty, "unable to hydrate new literal interface value", vnewlit, err)
}
}
// now we can set the value
vnewval := dereferencePointerValue(vnew)
voldval := dereferencePointerValue(v)
// make sure we can set the new value into the provided pointer
if varr {
// for arrays, append the new value into the array structure
if !voldval.CanSet() {
return utils.NewError(hydrateProperty, "unable to set array value", v, nil)
} else {
voldval.Set(reflect.Append(voldval, vnewval))
}
} else if vlit {
// for literals, set the dereferenced value
if !voldval.CanSet() {
return utils.NewError(hydrateProperty, "unable to set literal value", v, nil)
} else {
voldval.Set(vnewval)
}
} else if !v.CanSet() {
return utils.NewError(hydrateProperty, "unable to set pointer value", v, nil)
} else {
// everything else should be a pointer, set it directly
v.Set(vnew)
}
return nil
}
func hydrateNestedComponent(v reflect.Value, component *token) error {
// create a new object to hold the property value
var vnew, varr = newValue(v)
if err := hydrateComponent(vnew, component); err != nil {
return utils.NewError(hydrateNestedComponent, "unable to decode component", component, err)
}
if varr {
// for arrays, append the new value into the array structure
voldval := dereferencePointerValue(v)
if !voldval.CanSet() {
return utils.NewError(hydrateNestedComponent, "unable to set array value", v, nil)
} else {
voldval.Set(reflect.Append(voldval, vnew))
}
} else if !v.CanSet() {
return utils.NewError(hydrateNestedComponent, "unable to set pointer value", v, nil)
} else {
// everything else should be a pointer, set it directly
v.Set(vnew)
}
return nil
}
func hydrateProperties(v reflect.Value, component *token) error {
vdref := dereferencePointerValue(v)
vtype := vdref.Type()
vkind := vdref.Kind()
if vkind != reflect.Struct {
return utils.NewError(hydrateProperties, "unable to hydrate properties of non-struct", v, nil)
}
n := vtype.NumField()
for i := 0; i < n; i++ {
prop := properties.PropertyFromStructField(vtype.Field(i))
if prop == nil {
continue // skip if field is ignored
}
vfield := vdref.Field(i)
// first try to hydrate property values
if properties, ok := component.properties[prop.Name]; ok {
for _, prop := range properties {
if err := hydrateProperty(vfield, prop); err != nil {
msg := fmt.Sprintf("unable to hydrate property %s", prop.Name)
return utils.NewError(hydrateProperties, msg, v, err)
}
}
}
// then try to hydrate components
vtemp, _ := newValue(vfield)
if tag, err := extractTagFromValue(vtemp); err != nil {
msg := fmt.Sprintf("unable to extract tag from property %s", prop.Name)
return utils.NewError(hydrateProperties, msg, v, err)
} else if components, ok := component.components[tag]; ok {
for _, comp := range components {
if err := hydrateNestedComponent(vfield, comp); err != nil {
msg := fmt.Sprintf("unable to hydrate component %s", prop.Name)
return utils.NewError(hydrateProperties, msg, v, err)
}
}
}
}
return nil
}
func hydrateComponent(v reflect.Value, component *token) error {
if tag, err := extractTagFromValue(v); err != nil {
return utils.NewError(hydrateComponent, "error extracting tag from value", component, err)
} else if tag != component.name {
msg := fmt.Sprintf("expected %s and found %s", tag, component.name)
return utils.NewError(hydrateComponent, msg, component, nil)
} else if err := hydrateProperties(v, component); err != nil {
return utils.NewError(hydrateComponent, "unable to hydrate properties", component, err)
}
return nil
}
func hydrateComponents(v reflect.Value, components []*token) error {
vdref := dereferencePointerValue(v)
for i, component := range components {
velem := reflect.New(vdref.Type().Elem())
if err := hydrateComponent(velem, component); err != nil {
msg := fmt.Sprintf("unable to hydrate component %d", i)
return utils.NewError(hydrateComponent, msg, component, err)
} else {
v.Set(reflect.Append(vdref, velem))
}
}
return nil
}
func hydrateValue(v reflect.Value, component *token) error {
if !v.IsValid() || v.Kind() != reflect.Ptr {
return utils.NewError(hydrateValue, "unmarshal target must be a valid pointer", v, nil)
}
// handle any encodable properties
if encoder, isprop := v.Interface().(properties.CanEncodeName); isprop {
if name, err := encoder.EncodeICalName(); err != nil {
return utils.NewError(hydrateValue, "unable to lookup property name", v, err)
} else if properties, found := component.properties[name]; !found || len(properties) == 0 {
return utils.NewError(hydrateValue, "no matching propery values found for "+string(name), v, nil)
} else if len(properties) > 1 {
return utils.NewError(hydrateValue, "more than one property value matches single property interface", v, nil)
} else {
return hydrateProperty(v, properties[0])
}
}
// handle components
vkind := dereferencePointerValue(v).Kind()
if tag, err := extractTagFromValue(v); err != nil {
return utils.NewError(hydrateValue, "unable to extract component tag", v, err)
} else if components, found := component.components[tag]; !found || len(components) == 0 {
msg := fmt.Sprintf("unable to find matching component for %s", tag)
return utils.NewError(hydrateValue, msg, v, nil)
} else if vkind == reflect.Array || vkind == reflect.Slice {
return hydrateComponents(v, components)
} else if len(components) > 1 {
return utils.NewError(hydrateValue, "non-array interface provided but more than one component found!", v, nil)
} else {
return hydrateComponent(v, components[0])
}
}
// decodes encoded icalendar data into a native interface
func Unmarshal(encoded string, into interface{}) error {
if component, err := tokenize(encoded); err != nil {
return utils.NewError(Unmarshal, "unable to tokenize encoded data", encoded, err)
} else {
return hydrateValue(reflect.ValueOf(into), component)
}
}