-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathaddress_formatter.go
191 lines (158 loc) · 6.25 KB
/
address_formatter.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
package addrFmt
import (
"errors"
"github.com/cbroglie/mustache"
"html"
"log"
"regexp"
"strings"
)
var commonCountryCodeAliases = map[string]string{"UK": "GB"}
var validReplacementComponents = []string{"state"}
var requiredAddressProperties = []string{"road", "postcode"}
type replacement struct {
pattern *regexp.Regexp
replace string
}
var replacements = []replacement{
{pattern: regexp.MustCompile(`[\},\s]+$`), replace: ""},
{pattern: regexp.MustCompile(`(?m)^[,\s]+`), replace: ""},
{pattern: regexp.MustCompile(`(?m)^- `), replace: ""}, // line starting with dash due to a parameter missing
{pattern: regexp.MustCompile(`,\s*,`), replace: ", "}, // multiple commas to one
{pattern: regexp.MustCompile(`[[:blank:]]+,[[:blank:]]+/`), replace: ", "}, // one horiz whitespace behind comma
{pattern: regexp.MustCompile(`[[:blank:]][[:blank:]]+`), replace: " "}, // multiple horiz whitespace to one
{pattern: regexp.MustCompile(`[[:blank:]]\n`), replace: "\n"}, // horiz whitespace, newline to newline
{pattern: regexp.MustCompile(`\n,`), replace: "\n"}, // newline comma to just newline
{pattern: regexp.MustCompile(`,,+`), replace: ","}, // multiple commas to one
{pattern: regexp.MustCompile(`,\n`), replace: "\n"}, // comma newline to just newline
{pattern: regexp.MustCompile(`\n[[:blank:]]+`), replace: "\n"}, // newline plus space to newline
{pattern: regexp.MustCompile(`\n\n+`), replace: "\n"}, // multiple newline to one
}
// FormatAddress formats an Address object based on it
func FormatAddress(address *Address, config *Config) (interface{}, error) {
// ease up the Address into a map to make it accessible via index
addressMap, err := addressToMap(address)
if err != nil {
return nil, err
}
template := findTemplate(address.CountryCode, config.Templates)
render, err := applyTemplate(addressMap, template, config.Templates)
if err != nil {
return nil, err
}
return getOutput(render, config.OutputFormat)
}
func applyTemplate(addressMap addressMap, template template, templates map[string]template) (string, error) {
templateText := chooseTemplateText(addressMap, template, templates)
render, _ := mustache.Render(templateText, getRenderInput(addressMap))
// unescape render to enforce official mustache HTML escaping rules
render = html.UnescapeString(render)
// todo: postformat replacements rely on a clean render but can mess it up again... (constraint by OpenCageData)
var err error
render, err = cleanupRender(render)
if err != nil {
return "", err
}
render = applyPostformatReplacements(render, template)
render, err = cleanupRender(render)
if err != nil {
return "", err
}
return render, nil
}
var possibilitiesRegExp = regexp.MustCompile(`\s*\|\|\s*`)
func getRenderInput(addressMap addressMap) map[string]interface{} {
input := make(map[string]interface{})
for k, v := range addressMap {
input[k] = v
}
input["first"] = func(t string, f func(string) (string, error)) (string, error) {
t, _ = f(t)
possibilities := possibilitiesRegExp.Split(t, -1)
for _, possibility := range possibilities {
if possibility != "" {
return possibility, nil
}
}
return "", nil
}
return input
}
func chooseTemplateText(address addressMap, template template, templates map[string]template) string {
var templateText string
templateValue, isTemplateMap := template.(map[string]interface{})
if isTemplateMap {
missingPropertyCount := 0
for _, requiredProperty := range requiredAddressProperties {
if _, hasProperty := address[requiredProperty]; !hasProperty {
missingPropertyCount++
}
}
if missingPropertyCount == len(requiredAddressProperties) {
if fallbackTemplate, hasFallbackTemplate := templateValue["fallback_template"]; hasFallbackTemplate {
templateText = fallbackTemplate.(string)
} else if defaultTemplate, hasDefaultTemplate := templates["default"]; hasDefaultTemplate {
templateText = defaultTemplate.(map[string]interface{})["fallback_template"].(string)
}
} else // has country specific template
if addressTemplate, hasAddressTemplate := templateValue["address_template"]; hasAddressTemplate {
templateText = addressTemplate.(string)
} else // has default template
if defaultTemplate, hasDefaultTemplate := templates["default"]; hasDefaultTemplate {
templateText = defaultTemplate.(map[string]interface{})["address_template"].(string)
}
} else {
templateText = template.(string)
}
return templateText
}
func cleanupRender(render string) (string, error) {
for _, replacement := range replacements {
render = replacement.pattern.ReplaceAllString(render, replacement.replace)
render = dedupe(strings.Split(render, "\n"), "\n", func(s string) string {
return dedupe(strings.Split(s, ", "), ", ", func(s string) string {
return s
})
})
}
return strings.TrimSpace(render), nil
}
func dedupe(chunks []string, glue string, modifier func(s string) string) string {
seen := make(map[string]bool)
result := make([]string, 0)
for _, chunk := range chunks {
chunk = strings.TrimSpace(chunk)
if strings.ToLower(chunk) == "new york" {
seen[chunk] = true
result = append(result, chunk)
} else if seenChunk, hasChunk := seen[chunk]; !hasChunk || !seenChunk {
seen[chunk] = true
result = append(result, modifier(chunk))
}
}
return strings.Join(result, glue)
}
func applyPostformatReplacements(render string, template template) string {
if postformatReplacements, hasReplacements := template.(map[string]interface{})["postformat_replace"].([]interface{}); hasReplacements {
for _, replacement := range postformatReplacements {
r, err := regexp.Compile(replacement.([]interface{})[0].(string))
if err != nil {
log.Printf("Could not replace due to bad regexp: %v", err)
}
render = r.ReplaceAllString(render, replacement.([]interface{})[1].(string))
}
}
return render
}
func getOutput(render string, outputFormat OutputFormat) (interface{}, error) {
switch outputFormat {
case Array:
return strings.Split(render, "\n"), nil
case OneLine:
return strings.Replace(render, "\n", ", ", -1), nil
case PostalFormat:
return render + "\n", nil
default:
return nil, errors.New("invalid output format")
}
}