-
Notifications
You must be signed in to change notification settings - Fork 1
/
csv.go
143 lines (122 loc) · 3.41 KB
/
csv.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
package rmap
import (
"bytes"
"fmt"
"sort"
"strings"
)
//RmapsToCSV takes multiple Rmap instances and returns as CSV bytes with header
//nested keys are stored as l1.l2.l3
func RmapsToCSV(rmaps []Rmap, separator string) ([]byte, error) {
header := map[string]interface{}{}
//Get header from first element
collectKeys(rmaps[0], nil, &header)
//Get sorted header keys
headerKeys := NewFromMap(header).KeysSliceString()
sort.Strings(headerKeys)
output := bytes.Buffer{}
//write sorted header to csv
output.Write(writeHeader(headerKeys, separator))
output.WriteString("\n")
for _, rm := range rmaps {
//each row starts with copy of header with all values set to struct{}
row := NewFromMap(header).Copy()
//fill row with values
if err := collectValues(rm, nil, &row.Mapa); err != nil {
return nil, err
}
//generate bytes for one row, sorted by header keys
rowBytes, err := writeValues(row, headerKeys, separator)
if err != nil {
return nil, err
}
output.Write(rowBytes)
output.WriteString("\n")
}
return output.Bytes(), nil
}
func writeHeader(keys []string, separator string) []byte {
return []byte(strings.Join(keys, separator))
}
func writeValues(input Rmap, headerKeys []string, separator string) ([]byte, error) {
rowData := make([]string, len(headerKeys))
for idx, key := range headerKeys {
val, err := input.Get(key)
if err != nil {
return nil, err
}
valS, isString := val.(string)
if isString {
if strings.Index(valS, separator) != -1 {
//, in string, wrap in ""
if strings.Index(valS, `"`) != -1 {
// " in string remove
valS = strings.Replace(valS, `"`, ``, -1)
}
rowData[idx] = `"` + valS + `"`
} else {
rowData[idx] = fmt.Sprintf("%v", val)
}
} else {
rowData[idx] = fmt.Sprintf("%v", val)
}
}
return []byte(strings.Join(rowData, separator)), nil
}
func collectValues(input Rmap, path []string, row *map[string]interface{}) error {
for k, v := range input.Mapa {
switch v.(type) {
case Rmap:
//nested Rmap, recurse
if err := collectValues(v.(Rmap), append(path, k), row); err != nil {
return err
}
case map[string]interface{}:
//nested map, recurse
if err := collectValues(NewFromMap(v.(map[string]interface{})), append(path, k), row); err != nil {
return err
}
default:
if err := processValue(v, append(path, k), row); err != nil {
return err
}
}
}
return nil
}
func processValue(value interface{}, path []string, row *map[string]interface{}) error {
key := strings.Join(path, ".")
_, exists := (*row)[key]
if !exists {
return fmt.Errorf("unexpected key: %s, not found in header", key)
}
switch value.(type) {
case string:
(*row)[key] = strings.Replace(value.(string), "\n", "", -1)
case float64:
(*row)[key] = value.(float64)
case int:
(*row)[key] = value.(int)
default:
//fallback
(*row)[key] = fmt.Sprintf("%v", value)
}
return nil
}
//fill keys map with keys present in input
//nested keys are returned in format a.b.c
func collectKeys(input Rmap, path []string, keys *map[string]interface{}) {
for k, v := range input.Mapa {
switch v.(type) {
case Rmap:
//nested Rmap, recurse
collectKeys(v.(Rmap), append(path, k), keys)
case map[string]interface{}:
//nested map, recurse
collectKeys(NewFromMap(v.(map[string]interface{})), append(path, k), keys)
default:
//anything else is just a key
(*keys)[strings.Join(append(path, k), ".")] = struct{}{}
}
}
}