-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathTemplate.stage.funcs.go
295 lines (258 loc) · 7.51 KB
/
Template.stage.funcs.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
package docxplate
import (
"bytes"
"encoding/xml"
"strings"
"sync"
)
// Collect and trigger placeholders with trigger but unset in `t.params`
// Placeholders with trigger `:empty` must be triggered
// otherwise they are left
func (t *Template) triggerMissingParams(xnode *xmlNode) {
if t.params == nil {
return
}
var triggerParams ParamList
xnode.Walk(func(n *xmlNode) {
if !n.isRowElement() || !n.HaveParams() {
return
}
p := NewParamFromRaw(n.AllContents())
if p != nil && p.Trigger != nil {
triggerParams = append(triggerParams, p)
}
})
if triggerParams == nil {
return
}
// make sure not to "tint" original t.params
_params := t.params
t.params = triggerParams
// do stuff only with filtered params
t.replaceSingleParams(xnode, true)
// back to original
t.params = _params
}
// Expand complex placeholders
func (t *Template) expandPlaceholders(xnode *xmlNode) {
if t.params == nil {
return
}
xnode.WalkWithEnd(func(nrow *xmlNode) bool {
var once sync.Once
if nrow.isNew {
return false
}
if !nrow.isRowElement() {
return false
}
var max int
contents := nrow.AllContents()
rowParams := rowParams(contents)
rowPlaceholders := make(map[string]*placeholder)
for _, rowParam := range rowParams {
var placeholderType placeholderType
if len(rowParam.Separator) > 0 {
placeholderType = inlinePlaceholder
} else {
placeholderType = rowPlaceholder
}
var formatter, trigger, params string
if rowParam.Formatter != nil {
formatter = rowParam.Formatter.String()
}
if rowParam.Trigger != nil {
trigger = rowParam.Trigger.String()
}
if rowParam.Formatter != nil || rowParam.Trigger != nil {
params = " " + formatter + trigger
}
paramData := t.params.FindAllByKey(rowParam.AbsoluteKey)
if len(paramData) == 0 {
continue
}
placeholders := make([]string, paramData[len(paramData)-1].Index)
for _, param := range paramData {
placeholders[param.Index-1] = "{{" + param.AbsoluteKey + params + "}}"
}
rowPlaceholders[rowParam.RowPlaceholder] = &placeholder{
Type: placeholderType,
Placeholders: placeholders,
Separator: strings.TrimLeft(rowParam.Separator, " "),
}
if max < len(placeholders) {
max = len(placeholders)
}
}
nnews := make([]*xmlNode, max)
for oldPlaceholder, newPlaceholder := range rowPlaceholders {
switch newPlaceholder.Type {
case inlinePlaceholder:
nrow.Walk(func(n *xmlNode) {
if !inSlice(n.XMLName.Local, []string{"w-t"}) || len(n.Content) == 0 {
return
}
n.Content = bytes.ReplaceAll(n.Content, []byte(oldPlaceholder), []byte(strings.Join(newPlaceholder.Placeholders, newPlaceholder.Separator)))
})
case rowPlaceholder:
defer once.Do(func() {
nrow.delete()
})
for i := max - 1; i >= 0; i-- {
if nnews[i] == nil {
nnews[i] = nrow.cloneAndAppend()
}
nnews[i].Walk(func(n *xmlNode) {
if !inSlice(n.XMLName.Local, []string{"w-t"}) || len(n.Content) == 0 {
return
}
replaceData := oldPlaceholder
if i < len(newPlaceholder.Placeholders) && newPlaceholder.Placeholders[i] != "" {
replaceData = newPlaceholder.Placeholders[i]
}
n.Content = bytes.ReplaceAll(n.Content, []byte(oldPlaceholder), []byte(replaceData))
})
}
}
}
return true
})
}
// Replace single params by type
func (t *Template) replaceSingleParams(xnode *xmlNode, triggerParamOnly bool) {
paramAbsoluteKeyMap := map[string]*Param{}
t.params.Walk(func(p *Param) {
if p.Type != StringParam && p.Type != ImageParam {
return
}
paramAbsoluteKeyMap[p.AbsoluteKey] = p
})
xnode.Walk(func(n *xmlNode) {
for i := range n.Attrs {
for _, key := range t.GetAttrParam(n.Attrs[i].Value) {
p, ok := paramAbsoluteKeyMap[key]
if !ok {
continue
}
n.Attrs[i].Value = string(p.replaceIn([]byte(n.Attrs[i].Value)))
}
}
for _, key := range n.GetContentPrefixList() {
p, ok := paramAbsoluteKeyMap[key]
if !ok {
continue
}
t.replaceAndRunTrigger(p, n, triggerParamOnly)
}
})
}
func (t *Template) replaceAndRunTrigger(p *Param, n *xmlNode, triggerParamOnly bool) {
// Trigger: does placeholder have trigger
if p.Trigger = p.extractTriggerFrom(n.Content); p.Trigger != nil {
defer func() {
p.RunTrigger(n)
}()
}
if triggerParamOnly {
return
}
// Repalce by type
switch p.Type {
case StringParam:
if p.Formatter = p.extractFormatter(n.Content); p.Formatter != nil {
result := p.Formatter.ApplyFormat(p.Formatter.Format, []byte(p.Value))
p.Value = string(result)
}
t.replaceTextParam(n, p)
case ImageParam:
t.replaceImageParams(n, p)
}
}
// Enhance some markup (removed when building XML in the end)
// so easier to find some element
func (t *Template) enhanceMarkup(xnode *xmlNode) {
// List items - add list item node `w-p` attributes
// so it's recognized as listitem
xnode.Walk(func(n *xmlNode) {
if n.Tag() != "w-p" {
return
}
isListItem, listID := n.IsListItem()
if !isListItem {
return
}
// n.XMLName.Local = "w-item"
n.Attrs = append(n.Attrs, xml.Attr{
Name: xml.Name{Local: "list-id"},
Value: listID,
})
})
}
// new line variable for reuse
var nl = []byte("\n")
// Enhance content
func (t *Template) enhanceContent(xnode *xmlNode) {
// New lines from text as docx new lines
xnode.Walk(func(n *xmlNode) {
if !n.isSingle() {
return
}
if !bytes.Contains(n.Content, nl) {
return
}
nrow := n.closestUp([]string{"w-p"})
// log.Printf("NEW LINE: %s..%s [%q] %d new lines", aurora.Cyan(nrow.Tag()), aurora.Blue(n.Tag()), aurora.Yellow(n.Content), bytes.Count(n.Content, nl))
parts := bytes.Split(n.Content, nl)
for i, buf := range parts {
// clone the original node to preserve styles and append the cloned node
nlast := nrow.cloneAndAppend()
// first and last node can hold other text node prefixes, skip
if i >= 1 && i <= len(parts) {
nlast.Walk(func(n2 *xmlNode) {
if n2.isSingle() && len(n2.Content) > 0 && !bytes.Contains(n.Content, n2.Content) {
// delete all other text nodes because we need the same text node
n2.delete()
}
})
}
nlast.ReplaceInContents(n.Content, buf)
// nlast.printTree("NROW")
}
// delete the original node after cloning and adjusting (otherwise it shows at the end)
nrow.delete()
})
}
// This func is fixing broken placeholders by merging "w-t" nodes.
// "w-p" (Record) can hold multiple "w-r". And "w-r" holts "w-t" node
// -
// If these nodes not fixed than params replace can not be done as
// replacer process nodes one by one
func (t *Template) fixBrokenPlaceholders(xnode *xmlNode) {
xnode.Walk(func(nrow *xmlNode) {
if !nrow.isRowElement() {
return
}
var brokenNode *xmlNode
nrow.Walk(func(n *xmlNode) {
// broken node state? merge next nodes
if !n.isSingle() && len(n.AllContents()) > 0 {
// fmt.Printf("\t RESET -- %s->%s [%s]\n", n.parent.Tag(), aurora.Blue(n.Tag()), aurora.Red(n.AllContents()))
brokenNode = nil
return
}
if brokenNode != nil {
// fmt.Printf("OK [%s] + [%s]\n", aurora.Green(brokenNode.AllContents()), aurora.Green(n.AllContents()))
brokenNode.Content = append(brokenNode.Content, n.AllContents()...)
// aurora.Magenta("[%s] %v -- %v -- %v -- %v", brokenNode.Content, brokenNode.Tag(), brokenNode.parent.Tag(), brokenNode.parent.parent.Tag(), brokenNode.parent.parent.parent.Tag())
n.delete()
return
}
if t.matchBrokenLeftPlaceholder(string(n.Content)) {
// nrow.printTree("BROKEN")
brokenNode = n
return
}
brokenNode = nil
})
})
}