-
Notifications
You must be signed in to change notification settings - Fork 9
/
template.vbs
368 lines (299 loc) · 11.1 KB
/
template.vbs
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
357
358
359
360
361
362
363
364
365
366
367
368
'
' Template module
'
' Author: Jardel Weyrich <jardel@teltecsolutions.com.br>
'
Option Explicit
'----------------------------------------------------------------------------------------
Class TemplateTag
Public TagName
Public Offset
Public Length
Sub Class_Initialize()
End Sub
Sub Class_Terminate()
End Sub
Function Init(tagName, offset, length)
Me.TagName = tagName
Me.Offset = offset
Me.Length = length
Set Init = Me
End Function
End Class
'----------------------------------------------------------------------------------------
Class IfTag
Public Tag
Public VariableName
Sub Class_Initialize()
Me.VariableName = vbEmpty
End Sub
Sub Class_Terminate()
Set Me.Tag = Nothing
End Sub
Function Init(offset, length, variableName)
Set Me.Tag = New TemplateTag.Init("if", offset, length)
Me.VariableName = variableName
Set Init = Me
End Function
End Class
'----------------------------------------------------------------------------------------
Class EndIfTag
Public Tag
Sub Class_Initialize()
End Sub
Sub Class_Terminate()
Set Me.Tag = Nothing
End Sub
Function Init(offset, length)
Set Me.Tag = new TemplateTag.Init("endif", offset, length)
Set Init = Me
End Function
End Class
'----------------------------------------------------------------------------------------
Class ConditionBlock
Public Context
Public OpenTag
Public CloseTag
Sub Class_Initialize()
End Sub
Sub Class_Terminate()
Set Me.Context = Nothing
Set Me.OpenTag = Nothing
Set Me.CloseTag = Nothing
End Sub
' context - Dictionary
' openTag - IfTag
' CloseTag - EndIfTag
Function Init(ByRef context, ByRef openTag)
Set Me.Context = context
Set Me.OpenTag = openTag
Set Init = Me
End Function
Function Close(ByRef closeTag)
Set Me.CloseTag = closeTag
Set Close = Me
End Function
Function Evaluate()
Dim variableName: variableName = Me.OpenTag.VariableName
Dim hasVariableName: hasVariableName = VarType(variableName) <> vbEmpty
If Not hasVariableName Then
Evaluate = False
Exit Function
End If
Dim variableValue: variableValue = Me.Context.Item(variableName)
If VarType(variableValue) = vbEmpty Then
Call Err.Raise(5000, "ConditionBlock.Evaluate", "Undefined variable: " + variableName)
End If
'LogDebug("variableValue=" + CStr(variableValue))
Evaluate = CBool(variableValue)
End Function
End Class
'----------------------------------------------------------------------------------------
' Arguments:
' templateStr - String containing the template data.
' contextDict - Dictionary containing all variables to be used during the template processing.
' Return:
' Processed template
Function ProcessTemplateTags(ByRef templateStr, ByRef contextDict)
Const vbTextCompare = 1
If IsNullOrEmptyStr(templateStr) Then
ProcessTemplateTags = ""
Exit Function
End If
Dim partialTemplateStr: partialTemplateStr = ""
Dim rendered: rendered = ""
'
' Find tags
'
Dim templateSize: templateSize = Len(templateStr)
Dim currentPosition: currentPosition = 1
Dim insideConditionBlock: insideConditionBlock = False
Dim conditionBlock: Set conditionBlock = Nothing
Dim conditionResult: conditionResult = False
Do While currentPosition < templateSize
Dim startPosition: startPosition = InStr(currentPosition, templateStr, "{%", vbTextCompare)
If startPosition = 0 Then
' No more template tags to process.
partialTemplateStr = Mid(templateStr, currentPosition, templateSize - currentPosition)
'LogDebug("partialTemplateStr[no_more_tags] = " + partialTemplateStr)
rendered = rendered + partialTemplateStr
Exit Do
End If
Dim endPosition: endPosition = InStr(startPosition, templateStr, "%}", vbTextCompare)
If endPosition = 0 Then
LogError("Missing '%}'" & vbCrlf)
Exit Do
End If
partialTemplateStr = Mid(templateStr, currentPosition, startPosition - currentPosition)
'LogDebug("partialTemplateStr[found_tag] = " + partialTemplateStr)
endPosition = endPosition + 2 ' +2 because of "%}"
currentPosition = endPosition
Dim tagStr: tagStr = Mid(templateStr, startPosition, endPosition - startPosition)
'LogDebug("Tag = " + tagStr)
'
' Parse tag
'
' To debug the RegEx you can use https://myregextester.com/
Dim re
Set re = new RegExp
re.Global = True
re.Multiline = False
re.Pattern = "{%\s*(?:(.+?)\s+(.+?)?|(.+?))\s*%}" ' keyword <space(s)> argument | keyword
Dim matches
Set matches = re.Execute(tagStr)
'LogDebug("matches.Count=" & matches.Count)
Dim match
For Each match In matches
Dim matchOffset: matchOffset = startPosition + match.FirstIndex
Dim matchLength: matchLength = match.Length
'LogDebug("match.SubMatches.Count=" + CStr(match.SubMatches.Count))
'Dim submatch
'For Each submatch In match.SubMatches
' LogDebug("SubMatch=" + submatch)
'Next
Dim tagName: tagName = IIf(match.SubMatches.Count > 0, LCase(match.SubMatches(0)), vbEmpty)
'LogDebug("tagName=" + tagName)
Dim variableName: variableName = IIf(match.SubMatches.Count > 1, match.SubMatches(1), vbEmpty)
Dim hasVariableName: hasVariableName = VarType(variableName) <> vbEmpty
'LogDebug("variableName=" + variableName + " hasVariableName=" + CStr(hasVariableName))
Dim variableValue: variableValue = IIf(hasVariableName, contextDict.Item(variableName), vbEmpty)
Dim hasVariableValue: hasVariableValue = variableValue <> vbEmpty
'LogDebug("variableValue=" + CStr(variableValue) + " hasVariableValue=" + CStr(hasVariableValue))
'LogDebug("MATCH:" _
' + " matchOffset=" + CStr(matchOffset) _
' + ", matchLength=" + CStr(matchLength) _
' + ", tagName=" + tagName _
' + ", variableName=" + IIf(hasVariableName, CStr(variableName), "<vbEmpty>") _
' + ", variableValue=" + IIf(hasVariableValue, CStr(variableValue), "<vbEmpty>") _
')
Dim tmpTag
Select Case tagName
Case "if"
If insideConditionBlock Then
Call Err.Raise(5000, "ProcessTemplateTags", "The processor doesn't supported nested conditions yet.")
End If
'LogDebug("partialTemplateStr(@if) = " + partialTemplateStr)
rendered = rendered + partialTemplateStr
Set tmpTag = New IfTag.Init(matchOffset, matchLength, variableName)
Set conditionBlock = New ConditionBlock.Init(contextDict, tmpTag)
conditionResult = conditionBlock.Evaluate()
insideConditionBlock = True
Case "endif"
If Not insideConditionBlock Then
Call Err.Raise(5000, "ProcessTemplateTags", "Found endif without previous if.")
End If
Set tmpTag = New EndIfTag.Init(matchOffset, matchLength)
conditionBlock.Close(tmpTag)
insideConditionBlock = False
If conditionResult Then
REM LogDebug("Starts: "+Cstr(conditionBlock.OpenTag.Tag.Offset + conditionBlock.OpenTag.Tag.Length))
REM LogDebug("Length: "+Cstr(conditionBlock.CloseTag.Tag.Offset - conditionBlock.OpenTag.Tag.Offset - conditionBlock.OpenTag.Tag.Length))
REM partialTemplateStr = Mid(templateStr, _
REM (conditionBlock.OpenTag.Tag.Offset + conditionBlock.OpenTag.Tag.Length), _
REM (conditionBlock.CloseTag.Tag.Offset - (conditionBlock.OpenTag.Tag.Offset - conditionBlock.OpenTag.Tag.Length)) _
REM )
'LogDebug("partialTemplateStr(@endif) = " + partialTemplateStr)
rendered = rendered + partialTemplateStr
End If
Case Else
If insideConditionBlock And conditionResult Then
' Process tag.
Else
' Do NOT process tag.
End If
End Select
Next
Loop
If insideConditionBlock Then
Call Err.Raise(5000, "ProcessTemplateTags", "Missing an endif?")
End If
ProcessTemplateTags = rendered
End Function
'----------------------------------------------------------------------------------------
' Arguments:
' templateStr - String containing the template data.
' contextDict - Dictionary containing all variables to be used during the template processing.
' Return:
' Processed template
Function BindTemplateVariables(ByRef templateStr, ByRef contextDict)
' To debug the RegEx you can use https://myregextester.com/
Dim re
Set re = new RegExp
re.Global = True
re.Multiline = True
re.Pattern = "{{(.+?)}}"
Dim matches
Set matches = re.Execute(templateStr)
'LogDebug("matches.Count=" & matches.Count)
Dim result: result = Array()
Dim displacement: displacement = 0
Dim match
For Each match In matches
Dim matchOffset: matchOffset = match.FirstIndex
Dim matchLength: matchLength = match.Length
Dim variableName: variableName = match.SubMatches(0)
Dim variableValue: variableValue = contextDict.Item(variableName)
Dim variableValueLength: variableValueLength = Len(variableValue)
'LogDebug("displacement = " + CStr(displacement) _
' + ", matchOffset = " + CStr(matchOffset) _
' + ", matchLength = " + CStr(matchLength) _
' + ", variableName = " + variableName _
' + ", variableValue = " + CStr(variableValue) _
' + ", variableValueLength = " + CStr(variableValueLength) _
')
templateStr = ReplaceRange(templateStr, matchOffset + displacement, matchLength, variableValue)
displacement = displacement + (variableValueLength - matchLength)
Next
BindTemplateVariables = templateStr
End Function
'----------------------------------------------------------------------------------------
Function ReadTemplateFromFile(templateFilePath)
If Not FileExists(templateFilePath) Then
LogError("Template file does not exist - " & templateFilePath & vbCrlf)
Exit Function
End If
Const ForReading = 1
Dim objFSO: Set objFSO = CreateObject("Scripting.FileSystemObject")
Dim objFile: Set objFile = objFSO.OpenTextFile(templateFilePath, ForReading)
Dim templateStr: templateStr = objFile.ReadAll
objFile.Close
Set objFile = Nothing
Set objFSO = Nothing
ReadTemplateFromFile = templateStr
End Function
'----------------------------------------------------------------------------------------
Function ReadTemplateFromFileUTF8(templateFilePath)
If Not FileExists(templateFilePath) Then
LogError("Template file does not exist - " & templateFilePath & vbCrlf)
Exit Function
End If
' ADODB.Stream file I/O constants
Const template_adTypeBinary = 1
Const template_adTypeText = 2
Dim objStream: Set objStream = CreateObject("ADODB.Stream")
objStream.Open
objStream.Type = template_adTypeText
objStream.Position = 0
' Use UTF-8 so that accents/diacritics actually work.
objStream.CharSet = "utf-8"
' Read file
objStream.LoadFromFile(templateFilePath)
Dim templateStr: templateStr = objStream.ReadText()
' Close it.
objStream.Close
Set objStream = Nothing
ReadTemplateFromFileUTF8 = templateStr
End Function
'----------------------------------------------------------------------------------------
' Arguments:
' templateStr - String containing the template data.
' contextDict - Dictionary containing all variables to be used during the template rendering.
' Return:
' Rendered template
Function RenderTemplate(ByRef templateStr, ByRef contextDict)
Dim rendered: rendered = templateStr
rendered = ProcessTemplateTags(rendered, contextDict)
rendered = BindTemplateVariables(rendered, contextDict)
RenderTemplate = rendered
End Function
'----------------------------------------------------------------------------------------