tdrpa.tdworker 1.1.9.3__py39-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. tdrpa/_tdxlwings/__init__.py +193 -0
  2. tdrpa/_tdxlwings/__pycache__/__init__.cpython-311.pyc +0 -0
  3. tdrpa/_tdxlwings/__pycache__/__init__.cpython-38.pyc +0 -0
  4. tdrpa/_tdxlwings/__pycache__/_win32patch.cpython-311.pyc +0 -0
  5. tdrpa/_tdxlwings/__pycache__/_win32patch.cpython-38.pyc +0 -0
  6. tdrpa/_tdxlwings/__pycache__/_xlwindows.cpython-311.pyc +0 -0
  7. tdrpa/_tdxlwings/__pycache__/_xlwindows.cpython-38.pyc +0 -0
  8. tdrpa/_tdxlwings/__pycache__/apps.cpython-311.pyc +0 -0
  9. tdrpa/_tdxlwings/__pycache__/apps.cpython-38.pyc +0 -0
  10. tdrpa/_tdxlwings/__pycache__/base_classes.cpython-311.pyc +0 -0
  11. tdrpa/_tdxlwings/__pycache__/base_classes.cpython-38.pyc +0 -0
  12. tdrpa/_tdxlwings/__pycache__/com_server.cpython-311.pyc +0 -0
  13. tdrpa/_tdxlwings/__pycache__/com_server.cpython-38.pyc +0 -0
  14. tdrpa/_tdxlwings/__pycache__/constants.cpython-311.pyc +0 -0
  15. tdrpa/_tdxlwings/__pycache__/constants.cpython-38.pyc +0 -0
  16. tdrpa/_tdxlwings/__pycache__/expansion.cpython-311.pyc +0 -0
  17. tdrpa/_tdxlwings/__pycache__/expansion.cpython-38.pyc +0 -0
  18. tdrpa/_tdxlwings/__pycache__/main.cpython-311.pyc +0 -0
  19. tdrpa/_tdxlwings/__pycache__/main.cpython-38.pyc +0 -0
  20. tdrpa/_tdxlwings/__pycache__/udfs.cpython-311.pyc +0 -0
  21. tdrpa/_tdxlwings/__pycache__/udfs.cpython-38.pyc +0 -0
  22. tdrpa/_tdxlwings/__pycache__/utils.cpython-311.pyc +0 -0
  23. tdrpa/_tdxlwings/__pycache__/utils.cpython-38.pyc +0 -0
  24. tdrpa/_tdxlwings/_win32patch.py +90 -0
  25. tdrpa/_tdxlwings/_xlmac.py +2240 -0
  26. tdrpa/_tdxlwings/_xlwindows.py +2518 -0
  27. tdrpa/_tdxlwings/addin/Dictionary.cls +474 -0
  28. tdrpa/_tdxlwings/addin/IWebAuthenticator.cls +71 -0
  29. tdrpa/_tdxlwings/addin/WebClient.cls +772 -0
  30. tdrpa/_tdxlwings/addin/WebHelpers.bas +3203 -0
  31. tdrpa/_tdxlwings/addin/WebRequest.cls +875 -0
  32. tdrpa/_tdxlwings/addin/WebResponse.cls +453 -0
  33. tdrpa/_tdxlwings/addin/xlwings.xlam +0 -0
  34. tdrpa/_tdxlwings/apps.py +35 -0
  35. tdrpa/_tdxlwings/base_classes.py +1092 -0
  36. tdrpa/_tdxlwings/cli.py +1306 -0
  37. tdrpa/_tdxlwings/com_server.py +385 -0
  38. tdrpa/_tdxlwings/constants.py +3080 -0
  39. tdrpa/_tdxlwings/conversion/__init__.py +103 -0
  40. tdrpa/_tdxlwings/conversion/framework.py +147 -0
  41. tdrpa/_tdxlwings/conversion/numpy_conv.py +34 -0
  42. tdrpa/_tdxlwings/conversion/pandas_conv.py +184 -0
  43. tdrpa/_tdxlwings/conversion/standard.py +321 -0
  44. tdrpa/_tdxlwings/expansion.py +83 -0
  45. tdrpa/_tdxlwings/ext/__init__.py +3 -0
  46. tdrpa/_tdxlwings/ext/sql.py +73 -0
  47. tdrpa/_tdxlwings/html/xlwings-alert.html +71 -0
  48. tdrpa/_tdxlwings/js/xlwings.js +577 -0
  49. tdrpa/_tdxlwings/js/xlwings.ts +729 -0
  50. tdrpa/_tdxlwings/mac_dict.py +6399 -0
  51. tdrpa/_tdxlwings/main.py +5205 -0
  52. tdrpa/_tdxlwings/mistune/__init__.py +63 -0
  53. tdrpa/_tdxlwings/mistune/block_parser.py +366 -0
  54. tdrpa/_tdxlwings/mistune/inline_parser.py +216 -0
  55. tdrpa/_tdxlwings/mistune/markdown.py +84 -0
  56. tdrpa/_tdxlwings/mistune/renderers.py +220 -0
  57. tdrpa/_tdxlwings/mistune/scanner.py +121 -0
  58. tdrpa/_tdxlwings/mistune/util.py +41 -0
  59. tdrpa/_tdxlwings/pro/__init__.py +40 -0
  60. tdrpa/_tdxlwings/pro/_xlcalamine.py +536 -0
  61. tdrpa/_tdxlwings/pro/_xlofficejs.py +146 -0
  62. tdrpa/_tdxlwings/pro/_xlremote.py +1293 -0
  63. tdrpa/_tdxlwings/pro/custom_functions_code.js +150 -0
  64. tdrpa/_tdxlwings/pro/embedded_code.py +60 -0
  65. tdrpa/_tdxlwings/pro/udfs_officejs.py +549 -0
  66. tdrpa/_tdxlwings/pro/utils.py +199 -0
  67. tdrpa/_tdxlwings/quickstart.xlsm +0 -0
  68. tdrpa/_tdxlwings/quickstart_addin.xlam +0 -0
  69. tdrpa/_tdxlwings/quickstart_addin_ribbon.xlam +0 -0
  70. tdrpa/_tdxlwings/quickstart_fastapi/main.py +47 -0
  71. tdrpa/_tdxlwings/quickstart_fastapi/requirements.txt +3 -0
  72. tdrpa/_tdxlwings/quickstart_standalone.xlsm +0 -0
  73. tdrpa/_tdxlwings/reports.py +12 -0
  74. tdrpa/_tdxlwings/rest/__init__.py +1 -0
  75. tdrpa/_tdxlwings/rest/api.py +368 -0
  76. tdrpa/_tdxlwings/rest/serializers.py +103 -0
  77. tdrpa/_tdxlwings/server.py +14 -0
  78. tdrpa/_tdxlwings/udfs.py +775 -0
  79. tdrpa/_tdxlwings/utils.py +777 -0
  80. tdrpa/_tdxlwings/xlwings-0.31.6.applescript +30 -0
  81. tdrpa/_tdxlwings/xlwings.bas +2061 -0
  82. tdrpa/_tdxlwings/xlwings_custom_addin.bas +2042 -0
  83. tdrpa/_tdxlwings/xlwingslib.cp38-win_amd64.pyd +0 -0
  84. tdrpa/tdworker/__init__.pyi +8 -0
  85. tdrpa/tdworker/_excel.pyi +703 -0
  86. tdrpa/tdworker/_img.pyi +173 -0
  87. tdrpa/tdworker/_os.pyi +46 -0
  88. tdrpa/tdworker/_w.pyi +129 -0
  89. tdrpa/tdworker/_web.pyi +248 -0
  90. tdrpa/tdworker/_winE.pyi +246 -0
  91. tdrpa/tdworker/_winK.pyi +74 -0
  92. tdrpa/tdworker/_winM.pyi +117 -0
  93. tdrpa/tdworker.cp39-win_amd64.pyd +0 -0
  94. tdrpa.tdworker-1.1.9.3.dist-info/METADATA +25 -0
  95. tdrpa.tdworker-1.1.9.3.dist-info/RECORD +97 -0
  96. tdrpa.tdworker-1.1.9.3.dist-info/WHEEL +5 -0
  97. tdrpa.tdworker-1.1.9.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3203 @@
1
+ Attribute VB_Name = "WebHelpers"
2
+ ''
3
+ ' CHANGES:
4
+ ' * ParseIso and ConvertToIso have been changed to not do any timezone conversion
5
+ ' * ConvertToJson has been changed to add support for vbError
6
+ '
7
+ ' WebHelpers v4.1.6
8
+ ' (c) Tim Hall - https://github.com/VBA-tools/VBA-Web
9
+ '
10
+ ' Contains general-purpose helpers that are used throughout VBA-Web. Includes:
11
+ '
12
+ ' - Logging
13
+ ' - Converters and encoding
14
+ ' - Url handling
15
+ ' - Object/Dictionary/Collection/Array helpers
16
+ ' - Request preparation / handling
17
+ ' - Timing
18
+ ' - Mac
19
+ ' - Cryptography
20
+ ' - Converters (JSON, XML, Url-Encoded)
21
+ '
22
+ ' Errors:
23
+ ' 11000 - Error during parsing
24
+ ' 11001 - Error during conversion
25
+ ' 11002 - No matching converter has been registered
26
+ ' 11003 - Error while getting url parts
27
+ ' 11099 - XML format is not currently supported
28
+ '
29
+ ' @module WebHelpers
30
+ ' @author tim.hall.engr@gmail.com
31
+ ' @license MIT (http://www.opensource.org/licenses/mit-license.php)
32
+ '' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ '
33
+ Option Explicit
34
+
35
+ ' Contents:
36
+ ' 1. Logging
37
+ ' 2. Converters and encoding
38
+ ' 3. Url handling
39
+ ' 4. Object/Dictionary/Collection/Array helpers
40
+ ' 5. Request preparation / handling
41
+ ' 6. Timing
42
+ ' 7. Mac
43
+ ' 8. Cryptography
44
+ ' 9. Converters
45
+ ' VBA-JSON
46
+ ' VBA-UTC
47
+ ' AutoProxy
48
+ ' --------------------------------------------- '
49
+
50
+ ' Custom formatting uses the standard version of Application.Run,
51
+ ' which is incompatible with some Office applications (e.g. Word 2011 for Mac)
52
+ '
53
+ ' If you have compilation errors in ParseByFormat or ConvertToFormat,
54
+ ' you can disable custom formatting by setting the following compiler flag to False
55
+ #Const EnableCustomFormatting = True
56
+
57
+ ' === AutoProxy Headers
58
+ #If Mac Then
59
+ #ElseIf VBA7 Then
60
+
61
+ Private Declare PtrSafe Sub AutoProxy_CopyMemory Lib "KERNEL32" Alias "RtlMoveMemory" _
62
+ (ByVal AutoProxy_lpDest As LongPtr, ByVal AutoProxy_lpSource As LongPtr, ByVal AutoProxy_cbCopy As Long)
63
+ Private Declare PtrSafe Function AutoProxy_SysAllocString Lib "oleaut32" Alias "SysAllocString" _
64
+ (ByVal AutoProxy_pwsz As LongPtr) As LongPtr
65
+ Private Declare PtrSafe Function AutoProxy_GlobalFree Lib "KERNEL32" Alias "GlobalFree" _
66
+ (ByVal AutoProxy_p As LongPtr) As LongPtr
67
+ Private Declare PtrSafe Function AutoProxy_GetIEProxy Lib "WinHTTP.dll" Alias "WinHttpGetIEProxyConfigForCurrentUser" _
68
+ (ByRef AutoProxy_proxyConfig As AUTOPROXY_IE_PROXY_CONFIG) As Long
69
+ Private Declare PtrSafe Function AutoProxy_GetProxyForUrl Lib "WinHTTP.dll" Alias "WinHttpGetProxyForUrl" _
70
+ (ByVal AutoProxy_hSession As LongPtr, ByVal AutoProxy_pszUrl As LongPtr, ByRef AutoProxy_pAutoProxyOptions As AUTOPROXY_OPTIONS, ByRef AutoProxy_pProxyInfo As AUTOPROXY_INFO) As Long
71
+ Private Declare PtrSafe Function AutoProxy_HttpOpen Lib "WinHTTP.dll" Alias "WinHttpOpen" _
72
+ (ByVal AutoProxy_pszUserAgent As LongPtr, ByVal AutoProxy_dwAccessType As Long, ByVal AutoProxy_pszProxyName As LongPtr, ByVal AutoProxy_pszProxyBypass As LongPtr, ByVal AutoProxy_dwFlags As Long) As LongPtr
73
+ Private Declare PtrSafe Function AutoProxy_HttpClose Lib "WinHTTP.dll" Alias "WinHttpCloseHandle" _
74
+ (ByVal AutoProxy_hInternet As LongPtr) As Long
75
+
76
+ Private Type AUTOPROXY_IE_PROXY_CONFIG
77
+ AutoProxy_fAutoDetect As Long
78
+ AutoProxy_lpszAutoConfigUrl As LongPtr
79
+ AutoProxy_lpszProxy As LongPtr
80
+ AutoProxy_lpszProxyBypass As LongPtr
81
+ End Type
82
+ Private Type AUTOPROXY_OPTIONS
83
+ AutoProxy_dwFlags As Long
84
+ AutoProxy_dwAutoDetectFlags As Long
85
+ AutoProxy_lpszAutoConfigUrl As LongPtr
86
+ AutoProxy_lpvReserved As LongPtr
87
+ AutoProxy_dwReserved As Long
88
+ AutoProxy_fAutoLogonIfChallenged As Long
89
+ End Type
90
+ Private Type AUTOPROXY_INFO
91
+ AutoProxy_dwAccessType As Long
92
+ AutoProxy_lpszProxy As LongPtr
93
+ AutoProxy_lpszProxyBypass As LongPtr
94
+ End Type
95
+
96
+ #Else
97
+
98
+ Private Declare Sub AutoProxy_CopyMemory Lib "KERNEL32" Alias "RtlMoveMemory" _
99
+ (ByVal AutoProxy_lpDest As Long, ByVal AutoProxy_lpSource As Long, ByVal AutoProxy_cbCopy As Long)
100
+ Private Declare Function AutoProxy_SysAllocString Lib "oleaut32" Alias "SysAllocString" _
101
+ (ByVal AutoProxy_pwsz As Long) As Long
102
+ Private Declare Function AutoProxy_GlobalFree Lib "KERNEL32" Alias "GlobalFree" _
103
+ (ByVal AutoProxy_p As Long) As Long
104
+ Private Declare Function AutoProxy_GetIEProxy Lib "WinHTTP.dll" Alias "WinHttpGetIEProxyConfigForCurrentUser" _
105
+ (ByRef AutoProxy_proxyConfig As AUTOPROXY_IE_PROXY_CONFIG) As Long
106
+ Private Declare Function AutoProxy_GetProxyForUrl Lib "WinHTTP.dll" Alias "WinHttpGetProxyForUrl" _
107
+ (ByVal AutoProxy_hSession As Long, ByVal AutoProxy_pszUrl As Long, ByRef AutoProxy_pAutoProxyOptions As AUTOPROXY_OPTIONS, ByRef AutoProxy_pProxyInfo As AUTOPROXY_INFO) As Long
108
+ Private Declare Function AutoProxy_HttpOpen Lib "WinHTTP.dll" Alias "WinHttpOpen" _
109
+ (ByVal AutoProxy_pszUserAgent As Long, ByVal AutoProxy_dwAccessType As Long, ByVal AutoProxy_pszProxyName As Long, ByVal AutoProxy_pszProxyBypass As Long, ByVal AutoProxy_dwFlags As Long) As Long
110
+ Private Declare Function AutoProxy_HttpClose Lib "WinHTTP.dll" Alias "WinHttpCloseHandle" _
111
+ (ByVal AutoProxy_hInternet As Long) As Long
112
+
113
+ Private Type AUTOPROXY_IE_PROXY_CONFIG
114
+ AutoProxy_fAutoDetect As Long
115
+ AutoProxy_lpszAutoConfigUrl As Long
116
+ AutoProxy_lpszProxy As Long
117
+ AutoProxy_lpszProxyBypass As Long
118
+ End Type
119
+ Private Type AUTOPROXY_OPTIONS
120
+ AutoProxy_dwFlags As Long
121
+ AutoProxy_dwAutoDetectFlags As Long
122
+ AutoProxy_lpszAutoConfigUrl As Long
123
+ AutoProxy_lpvReserved As Long
124
+ AutoProxy_dwReserved As Long
125
+ AutoProxy_fAutoLogonIfChallenged As Long
126
+ End Type
127
+ Private Type AUTOPROXY_INFO
128
+ AutoProxy_dwAccessType As Long
129
+ AutoProxy_lpszProxy As Long
130
+ AutoProxy_lpszProxyBypass As Long
131
+ End Type
132
+
133
+ #End If
134
+
135
+ #If Mac Then
136
+ #Else
137
+ ' Constants for dwFlags of AUTOPROXY_OPTIONS
138
+ Const AUTOPROXY_AUTO_DETECT = 1
139
+ Const AUTOPROXY_CONFIG_URL = 2
140
+
141
+ ' Constants for dwAutoDetectFlags
142
+ Const AUTOPROXY_DETECT_TYPE_DHCP = 1
143
+ Const AUTOPROXY_DETECT_TYPE_DNS = 2
144
+ #End If
145
+ ' === End AutoProxy
146
+
147
+ ' === VBA-JSON Headers
148
+ ' === VBA-UTC Headers
149
+ #If Mac Then
150
+
151
+ #If VBA7 Then
152
+
153
+ ' 64-bit Mac (2016)
154
+ Private Declare PtrSafe Function utc_popen Lib "/usr/lib/libc.dylib" Alias "popen" _
155
+ (ByVal utc_Command As String, ByVal utc_Mode As String) As LongPtr
156
+ Private Declare PtrSafe Function utc_pclose Lib "/usr/lib/libc.dylib" Alias "pclose" _
157
+ (ByVal utc_File As LongPtr) As LongPtr
158
+ Private Declare PtrSafe Function utc_fread Lib "/usr/lib/libc.dylib" Alias "fread" _
159
+ (ByVal utc_Buffer As String, ByVal utc_Size As LongPtr, ByVal utc_Number As LongPtr, ByVal utc_File As LongPtr) As LongPtr
160
+ Private Declare PtrSafe Function utc_feof Lib "/usr/lib/libc.dylib" Alias "feof" _
161
+ (ByVal utc_File As LongPtr) As LongPtr
162
+
163
+ #Else
164
+
165
+ ' 32-bit Mac
166
+ Private Declare Function utc_popen Lib "libc.dylib" Alias "popen" _
167
+ (ByVal utc_Command As String, ByVal utc_Mode As String) As Long
168
+ Private Declare Function utc_pclose Lib "libc.dylib" Alias "pclose" _
169
+ (ByVal utc_File As Long) As Long
170
+ Private Declare Function utc_fread Lib "libc.dylib" Alias "fread" _
171
+ (ByVal utc_Buffer As String, ByVal utc_Size As Long, ByVal utc_Number As Long, ByVal utc_File As Long) As Long
172
+ Private Declare Function utc_feof Lib "libc.dylib" Alias "feof" _
173
+ (ByVal utc_File As Long) As Long
174
+
175
+ #End If
176
+
177
+ #ElseIf VBA7 Then
178
+
179
+ ' http://msdn.microsoft.com/en-us/library/windows/desktop/ms724421.aspx
180
+ ' http://msdn.microsoft.com/en-us/library/windows/desktop/ms724949.aspx
181
+ ' http://msdn.microsoft.com/en-us/library/windows/desktop/ms725485.aspx
182
+ Private Declare PtrSafe Function utc_GetTimeZoneInformation Lib "KERNEL32" Alias "GetTimeZoneInformation" _
183
+ (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION) As Long
184
+ Private Declare PtrSafe Function utc_SystemTimeToTzSpecificLocalTime Lib "KERNEL32" Alias "SystemTimeToTzSpecificLocalTime" _
185
+ (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION, utc_lpUniversalTime As utc_SYSTEMTIME, utc_lpLocalTime As utc_SYSTEMTIME) As Long
186
+ Private Declare PtrSafe Function utc_TzSpecificLocalTimeToSystemTime Lib "KERNEL32" Alias "TzSpecificLocalTimeToSystemTime" _
187
+ (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION, utc_lpLocalTime As utc_SYSTEMTIME, utc_lpUniversalTime As utc_SYSTEMTIME) As Long
188
+
189
+ #Else
190
+
191
+ Private Declare Function utc_GetTimeZoneInformation Lib "KERNEL32" Alias "GetTimeZoneInformation" _
192
+ (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION) As Long
193
+ Private Declare Function utc_SystemTimeToTzSpecificLocalTime Lib "KERNEL32" Alias "SystemTimeToTzSpecificLocalTime" _
194
+ (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION, utc_lpUniversalTime As utc_SYSTEMTIME, utc_lpLocalTime As utc_SYSTEMTIME) As Long
195
+ Private Declare Function utc_TzSpecificLocalTimeToSystemTime Lib "KERNEL32" Alias "TzSpecificLocalTimeToSystemTime" _
196
+ (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION, utc_lpLocalTime As utc_SYSTEMTIME, utc_lpUniversalTime As utc_SYSTEMTIME) As Long
197
+
198
+ #End If
199
+
200
+ #If Mac Then
201
+
202
+ #If VBA7 Then
203
+ Private Type utc_ShellResult
204
+ utc_Output As String
205
+ utc_ExitCode As LongPtr
206
+ End Type
207
+
208
+ #Else
209
+
210
+ Private Type utc_ShellResult
211
+ utc_Output As String
212
+ utc_ExitCode As Long
213
+ End Type
214
+
215
+ #End If
216
+
217
+ #Else
218
+
219
+ Private Type utc_SYSTEMTIME
220
+ utc_wYear As Integer
221
+ utc_wMonth As Integer
222
+ utc_wDayOfWeek As Integer
223
+ utc_wDay As Integer
224
+ utc_wHour As Integer
225
+ utc_wMinute As Integer
226
+ utc_wSecond As Integer
227
+ utc_wMilliseconds As Integer
228
+ End Type
229
+
230
+ Private Type utc_TIME_ZONE_INFORMATION
231
+ utc_Bias As Long
232
+ utc_StandardName(0 To 31) As Integer
233
+ utc_StandardDate As utc_SYSTEMTIME
234
+ utc_StandardBias As Long
235
+ utc_DaylightName(0 To 31) As Integer
236
+ utc_DaylightDate As utc_SYSTEMTIME
237
+ utc_DaylightBias As Long
238
+ End Type
239
+
240
+ #End If
241
+ ' === End VBA-UTC
242
+
243
+ Private Type json_Options
244
+ ' VBA only stores 15 significant digits, so any numbers larger than that are truncated
245
+ ' This can lead to issues when BIGINT's are used (e.g. for Ids or Credit Cards), as they will be invalid above 15 digits
246
+ ' See: http://support.microsoft.com/kb/269370
247
+ '
248
+ ' By default, VBA-JSON will use String for numbers longer than 15 characters that contain only digits
249
+ ' to override set `JsonConverter.JsonOptions.UseDoubleForLargeNumbers = True`
250
+ UseDoubleForLargeNumbers As Boolean
251
+
252
+ ' The JSON standard requires object keys to be quoted (" or '), use this option to allow unquoted keys
253
+ AllowUnquotedKeys As Boolean
254
+
255
+ ' The solidus (/) is not required to be escaped, use this option to escape them as \/ in ConvertToJson
256
+ EscapeSolidus As Boolean
257
+ End Type
258
+ Public JsonOptions As json_Options
259
+ ' === End VBA-JSON
260
+
261
+ #If Mac Then
262
+ #If VBA7 Then
263
+ Private Declare PtrSafe Function web_popen Lib "/usr/lib/libc.dylib" Alias "popen" (ByVal web_Command As String, ByVal web_Mode As String) As LongPtr
264
+ Private Declare PtrSafe Function web_pclose Lib "/usr/lib/libc.dylib" Alias "pclose" (ByVal web_File As LongPtr) As LongPtr
265
+ Private Declare PtrSafe Function web_fread Lib "/usr/lib/libc.dylib" Alias "fread" (ByVal web_OutStr As String, ByVal web_Size As LongPtr, ByVal web_Items As LongPtr, ByVal web_Stream As LongPtr) As LongPtr
266
+ Private Declare PtrSafe Function web_feof Lib "/usr/lib/libc.dylib" Alias "feof" (ByVal web_File As LongPtr) As LongPtr
267
+ #Else
268
+ Private Declare Function web_popen Lib "libc.dylib" Alias "popen" (ByVal web_Command As String, ByVal web_Mode As String) As Long
269
+ Private Declare Function web_pclose Lib "libc.dylib" Alias "pclose" (ByVal web_File As Long) As Long
270
+ Private Declare Function web_fread Lib "libc.dylib" Alias "fread" (ByVal web_OutStr As String, ByVal web_Size As Long, ByVal web_Items As Long, ByVal web_Stream As Long) As Long
271
+ Private Declare Function web_feof Lib "libc.dylib" Alias "feof" (ByVal web_File As Long) As Long
272
+ #End If
273
+ #End If
274
+
275
+ Public Const WebUserAgent As String = "VBA-Web v4.1.6 (https://github.com/VBA-tools/VBA-Web)"
276
+
277
+ ' @internal
278
+ Public Type ShellResult
279
+ Output As String
280
+ ExitCode As Long
281
+ End Type
282
+
283
+ Private web_pDocumentHelper As Object
284
+ Private web_pElHelper As Object
285
+ Private web_pConverters As Dictionary
286
+
287
+ ' --------------------------------------------- '
288
+ ' Types and Properties
289
+ ' --------------------------------------------- '
290
+
291
+ ''
292
+ ' Helper for common http status codes. (Use underlying status code for any codes not listed)
293
+ '
294
+ ' @example
295
+ ' ```VB.net
296
+ ' Dim Response As WebResponse
297
+ '
298
+ ' If Response.StatusCode = WebStatusCode.Ok Then
299
+ ' ' Ok
300
+ ' ElseIf Response.StatusCode = 418 Then
301
+ ' ' I'm a teapot
302
+ ' End If
303
+ ' ```
304
+ '
305
+ ' @property WebStatusCode
306
+ ' @param Ok `200`
307
+ ' @param Created `201`
308
+ ' @param NoContent `204`
309
+ ' @param NotModified `304`
310
+ ' @param BadRequest `400`
311
+ ' @param Unauthorized `401`
312
+ ' @param Forbidden `403`
313
+ ' @param NotFound `404`
314
+ ' @param RequestTimeout `408`
315
+ ' @param UnsupportedMediaType `415`
316
+ ' @param InternalServerError `500`
317
+ ' @param BadGateway `502`
318
+ ' @param ServiceUnavailable `503`
319
+ ' @param GatewayTimeout `504`
320
+ ''
321
+ Public Enum WebStatusCode
322
+ Ok = 200
323
+ Created = 201
324
+ NoContent = 204
325
+ NotModified = 304
326
+ BadRequest = 400
327
+ Unauthorized = 401
328
+ Forbidden = 403
329
+ NotFound = 404
330
+ RequestTimeout = 408
331
+ UnsupportedMediaType = 415
332
+ InternalServerError = 500
333
+ BadGateway = 502
334
+ ServiceUnavailable = 503
335
+ GatewayTimeout = 504
336
+ End Enum
337
+
338
+ ''
339
+ ' @property WebMethod
340
+ ' @param HttpGet
341
+ ' @param HttpPost
342
+ ' @param HttpGet
343
+ ' @param HttpGet
344
+ ' @param HttpGet
345
+ ' @default HttpGet
346
+ ''
347
+ Public Enum WebMethod
348
+ HttpGet = 0
349
+ HttpPost = 1
350
+ HttpPut = 2
351
+ HttpDelete = 3
352
+ HttpPatch = 4
353
+ HttpHead = 5
354
+ End Enum
355
+
356
+ ''
357
+ ' @property WebFormat
358
+ ' @param PlainText
359
+ ' @param Json
360
+ ' @param FormUrlEncoded
361
+ ' @param Xml
362
+ ' @param Custom
363
+ ' @default PlainText
364
+ ''
365
+ Public Enum WebFormat
366
+ PlainText = 0
367
+ Json = 1
368
+ FormUrlEncoded = 2
369
+ Xml = 3
370
+ Custom = 9
371
+ End Enum
372
+
373
+ ''
374
+ ' @property UrlEncodingMode
375
+ ' @param StrictUrlEncoding RFC 3986, ALPHA / DIGIT / "-" / "." / "_" / "~"
376
+ ' @param FormUrlEncoding ALPHA / DIGIT / "-" / "." / "_" / "*", (space) -> "+", &...; UTF-8 encoding
377
+ ' @param QueryUrlEncoding Subset of strict and form that should be suitable for non-form-urlencoded query strings
378
+ ' ALPHA / DIGIT / "-" / "." / "_"
379
+ ' @param CookieUrlEncoding strict / "!" / "#" / "$" / "&" / "'" / "(" / ")" / "*" / "+" /
380
+ ' "/" / ":" / "<" / "=" / ">" / "?" / "@" / "[" / "]" / "^" / "`" / "{" / "|" / "}"
381
+ ' @param PathUrlEncoding strict / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" / ":" / "@"
382
+ ''
383
+ Public Enum UrlEncodingMode
384
+ StrictUrlEncoding
385
+ FormUrlEncoding
386
+ QueryUrlEncoding
387
+ CookieUrlEncoding
388
+ PathUrlEncoding
389
+ End Enum
390
+
391
+ ''
392
+ ' Enable logging of requests and responses and other internal messages from VBA-Web.
393
+ ' Should be the first step in debugging VBA-Web if something isn't working as expected.
394
+ ' (Logs display in Immediate Window (`View > Immediate Window` or `ctrl+g`)
395
+ '
396
+ ' @example
397
+ ' ```VB.net
398
+ ' Dim Client As New WebClient
399
+ ' Client.BaseUrl = "https://api.example.com/v1/"
400
+ '
401
+ ' Dim RequestWithTypo As New WebRequest
402
+ ' RequestWithTypo.Resource = "peeple/{id}"
403
+ ' RequestWithType.AddUrlSegment "idd", 123
404
+ '
405
+ ' ' Enable logging before the request is executed
406
+ ' WebHelpers.EnableLogging = True
407
+ '
408
+ ' Dim Response As WebResponse
409
+ ' Set Response = Client.Execute(Request)
410
+ '
411
+ ' ' Immediate window:
412
+ ' ' --> Request - (Time)
413
+ ' ' GET https://api.example.com/v1/peeple/{id}
414
+ ' ' Headers...
415
+ ' '
416
+ ' ' <-- Response - (Time)
417
+ ' ' 404 ...
418
+ ' ```
419
+ '
420
+ ' @property EnableLogging
421
+ ' @type Boolean
422
+ ' @default False
423
+ ''
424
+ Public EnableLogging As Boolean
425
+
426
+ ''
427
+ ' Store currently running async requests
428
+ '
429
+ ' @property AsyncRequests
430
+ ' @type Dictionary
431
+ ''
432
+ Public AsyncRequests As Dictionary
433
+
434
+ ' ============================================= '
435
+ ' 1. Logging
436
+ ' ============================================= '
437
+
438
+ ''
439
+ ' Log message (when logging is enabled with `EnableLogging`)
440
+ ' with optional location where the message is coming from.
441
+ ' Useful when writing extensions to VBA-Web (like an `IWebAuthenticator`).
442
+ '
443
+ ' @example
444
+ ' ```VB.net
445
+ ' LogDebug "Executing request..."
446
+ ' ' -> VBA-Web: Executing request...
447
+ '
448
+ ' LogDebug "Executing request...", "Module.Function"
449
+ ' ' -> Module.Function: Executing request...
450
+ ' ```
451
+ '
452
+ ' @method LogDebug
453
+ ' @param {String} Message
454
+ ' @param {String} [From="VBA-Web"]
455
+ ''
456
+ Public Sub LogDebug(Message As String, Optional From As String = "VBA-Web")
457
+ If EnableLogging Then
458
+ Debug.Print From & ": " & Message
459
+ End If
460
+ End Sub
461
+
462
+ ''
463
+ ' Log warning (even when logging is disabled with `EnableLogging`)
464
+ ' with optional location where the message is coming from.
465
+ ' Useful when writing extensions to VBA-Web (like an `IWebAuthenticator`).
466
+ '
467
+ ' @example
468
+ ' ```VB.net
469
+ ' WebHelpers.LogWarning "Something could go wrong"
470
+ ' ' -> WARNING - VBA-Web: Something could go wrong
471
+ '
472
+ ' WebHelpers.LogWarning "Something could go wrong", "Module.Function"
473
+ ' ' -> WARNING - Module.Function: Something could go wrong
474
+ ' ```
475
+ '
476
+ ' @method LogWarning
477
+ ' @param {String} Message
478
+ ' @param {String} [From="VBA-Web"]
479
+ ''
480
+ Public Sub LogWarning(Message As String, Optional From As String = "VBA-Web")
481
+ Debug.Print "WARNING - " & From & ": " & Message
482
+ End Sub
483
+
484
+ ''
485
+ ' Log error (even when logging is disabled with `EnableLogging`)
486
+ ' with optional location where the message is coming from and error number.
487
+ ' Useful when writing extensions to VBA-Web (like an `IWebAuthenticator`).
488
+ '
489
+ ' @example
490
+ ' ```VB.net
491
+ ' WebHelpers.LogError "Something went wrong"
492
+ ' ' -> ERROR - VBA-Web: Something went wrong
493
+ '
494
+ ' WebHelpers.LogError "Something went wrong", "Module.Function"
495
+ ' ' -> ERROR - Module.Function: Something went wrong
496
+ '
497
+ ' WebHelpers.LogError "Something went wrong", "Module.Function", 100
498
+ ' ' -> ERROR - Module.Function: 100, Something went wrong
499
+ ' ```
500
+ '
501
+ ' @method LogError
502
+ ' @param {String} Message
503
+ ' @param {String} [From="VBA-Web"]
504
+ ' @param {Long} [ErrNumber=0]
505
+ ''
506
+ Public Sub LogError(Message As String, Optional From As String = "VBA-Web", Optional ErrNumber As Long = 0)
507
+ Dim web_ErrorValue As String
508
+ If ErrNumber <> 0 Then
509
+ web_ErrorValue = ErrNumber
510
+
511
+ If ErrNumber < 0 Then
512
+ web_ErrorValue = web_ErrorValue & " (" & (ErrNumber - vbObjectError) & " / " & VBA.LCase$(VBA.Hex$(ErrNumber)) & ")"
513
+ End If
514
+
515
+ web_ErrorValue = web_ErrorValue & ", "
516
+ End If
517
+
518
+ Debug.Print "ERROR - " & From & ": " & web_ErrorValue & Message
519
+ End Sub
520
+
521
+ ''
522
+ ' Log details of the request (Url, headers, cookies, body, etc.).
523
+ '
524
+ ' @method LogRequest
525
+ ' @param {WebClient} Client
526
+ ' @param {WebRequest} Request
527
+ ''
528
+ Public Sub LogRequest(Client As WebClient, Request As WebRequest)
529
+ If EnableLogging Then
530
+ Debug.Print "--> Request - " & Format(Now, "Long Time")
531
+ Debug.Print MethodToName(Request.Method) & " " & Client.GetFullUrl(Request)
532
+
533
+ Dim web_KeyValue As Dictionary
534
+ For Each web_KeyValue In Request.headers
535
+ Debug.Print web_KeyValue("Key") & ": " & web_KeyValue("Value")
536
+ Next web_KeyValue
537
+
538
+ For Each web_KeyValue In Request.Cookies
539
+ Debug.Print "Cookie: " & web_KeyValue("Key") & "=" & web_KeyValue("Value")
540
+ Next web_KeyValue
541
+
542
+ If Not IsEmpty(Request.Body) Then
543
+ Debug.Print vbNewLine & CStr(Request.Body)
544
+ End If
545
+
546
+ Debug.Print
547
+ End If
548
+ End Sub
549
+
550
+ ''
551
+ ' Log details of the response (Status, headers, content, etc.).
552
+ '
553
+ ' @method LogResponse
554
+ ' @param {WebClient} Client
555
+ ' @param {WebRequest} Request
556
+ ' @param {WebResponse} Response
557
+ ''
558
+ Public Sub LogResponse(Client As WebClient, Request As WebRequest, Response As WebResponse)
559
+ If EnableLogging Then
560
+ Dim web_KeyValue As Dictionary
561
+
562
+ Debug.Print "<-- Response - " & Format(Now, "Long Time")
563
+ Debug.Print Response.StatusCode & " " & Response.StatusDescription
564
+
565
+ For Each web_KeyValue In Response.headers
566
+ Debug.Print web_KeyValue("Key") & ": " & web_KeyValue("Value")
567
+ Next web_KeyValue
568
+
569
+ For Each web_KeyValue In Response.Cookies
570
+ Debug.Print "Cookie: " & web_KeyValue("Key") & "=" & web_KeyValue("Value")
571
+ Next web_KeyValue
572
+
573
+ Debug.Print vbNewLine & Response.Content & vbNewLine
574
+ End If
575
+ End Sub
576
+
577
+ ''
578
+ ' Obfuscate any secure information before logging.
579
+ '
580
+ ' @example
581
+ ' ```VB.net
582
+ ' Dim Password As String
583
+ ' Password = "Secret"
584
+ '
585
+ ' WebHelpers.LogDebug "Password = " & WebHelpers.Obfuscate(Password)
586
+ ' -> Password = ******
587
+ ' ```
588
+ '
589
+ ' @param {String} Secure Message to obfuscate
590
+ ' @param {String} [Character = *] Character to obfuscate with
591
+ ' @return {String}
592
+ ''
593
+ Public Function Obfuscate(Secure As String, Optional Character As String = "*") As String
594
+ Obfuscate = VBA.String$(VBA.Len(Secure), Character)
595
+ End Function
596
+
597
+ ' ============================================= '
598
+ ' 2. Converters and encoding
599
+ ' ============================================= '
600
+
601
+ '
602
+ ' Parse JSON value to `Dictionary` if it's an object or `Collection` if it's an array.
603
+ '
604
+ ' @method ParseJson
605
+ ' @param {String} Json JSON value to parse
606
+ ' @return {Dictionary|Collection}
607
+ '
608
+ ' (Implemented in VBA-JSON embedded below)
609
+
610
+ '
611
+ ' Convert `Dictionary`, `Collection`, or `Array` to JSON string.
612
+ '
613
+ ' @method ConvertToJson
614
+ ' @param {Dictionary|Collection|Array} Obj
615
+ ' @return {String}
616
+ '
617
+ ' (Implemented in VBA-JSON embedded below)
618
+
619
+ ''
620
+ ' Parse Url-Encoded value to `Dictionary`.
621
+ '
622
+ ' @method ParseUrlEncoded
623
+ ' @param {String} UrlEncoded Url-Encoded value to parse
624
+ ' @return {Dictionary} Parsed
625
+ ''
626
+ Public Function ParseUrlEncoded(Encoded As String) As Dictionary
627
+ Dim web_Items As Variant
628
+ Dim web_i As Integer
629
+ Dim web_Parts As Variant
630
+ Dim web_Key As String
631
+ Dim web_Value As Variant
632
+ Dim web_Parsed As New Dictionary
633
+
634
+ web_Items = VBA.Split(Encoded, "&")
635
+ For web_i = LBound(web_Items) To UBound(web_Items)
636
+ web_Parts = VBA.Split(web_Items(web_i), "=")
637
+
638
+ If UBound(web_Parts) - LBound(web_Parts) >= 1 Then
639
+ ' TODO: Handle numbers, arrays, and object better here
640
+ web_Key = UrlDecode(VBA.CStr(web_Parts(LBound(web_Parts))))
641
+ web_Value = UrlDecode(VBA.CStr(web_Parts(LBound(web_Parts) + 1)))
642
+
643
+ web_Parsed(web_Key) = web_Value
644
+ End If
645
+ Next web_i
646
+
647
+ Set ParseUrlEncoded = web_Parsed
648
+ End Function
649
+
650
+ ''
651
+ ' Convert `Dictionary`/`Collection` to Url-Encoded string.
652
+ '
653
+ ' @method ConvertToUrlEncoded
654
+ ' @param {Dictionary|Collection|Variant} Obj Value to convert to Url-Encoded string
655
+ ' @return {String} UrlEncoded string (e.g. a=123&b=456&...)
656
+ ''
657
+ Public Function ConvertToUrlEncoded(Obj As Variant, Optional EncodingMode As UrlEncodingMode = UrlEncodingMode.FormUrlEncoding) As String
658
+ Dim web_Encoded As String
659
+
660
+ If TypeOf Obj Is Collection Then
661
+ Dim web_KeyValue As Dictionary
662
+
663
+ For Each web_KeyValue In Obj
664
+ If VBA.Len(web_Encoded) > 0 Then: web_Encoded = web_Encoded & "&"
665
+ web_Encoded = web_Encoded & web_GetUrlEncodedKeyValue(web_KeyValue("Key"), web_KeyValue("Value"), EncodingMode)
666
+ Next web_KeyValue
667
+ Else
668
+ Dim web_Key As Variant
669
+
670
+ For Each web_Key In Obj.Keys()
671
+ If Len(web_Encoded) > 0 Then: web_Encoded = web_Encoded & "&"
672
+ web_Encoded = web_Encoded & web_GetUrlEncodedKeyValue(web_Key, Obj(web_Key), EncodingMode)
673
+ Next web_Key
674
+ End If
675
+
676
+ ConvertToUrlEncoded = web_Encoded
677
+ End Function
678
+
679
+ ''
680
+ ' Parse XML value to `Dictionary`.
681
+ '
682
+ ' _Note_ Currently, XML is not supported in 4.0.0 due to lack of Mac support.
683
+ ' An updated parser is being created that supports Mac and Windows,
684
+ ' but in order to avoid future breaking changes, ParseXml and ConvertToXml are not currently implemented.
685
+ '
686
+ ' See https://github.com/VBA-tools/VBA-Web/wiki/XML-Support-in-4.0 for details on how to use XML in Windows in the meantime.
687
+ '
688
+ ' @param {String} Encoded XML value to parse
689
+ ' @return {Dictionary|Object} Parsed
690
+ ' @throws 11099 - XML format is not currently supported
691
+ ''
692
+ Public Function ParseXml(Encoded As String) As Object
693
+ Dim web_ErrorMsg As String
694
+
695
+ web_ErrorMsg = "XML is not currently supported (An updated parser is being created that supports Mac and Windows)." & vbNewLine & _
696
+ "To use XML parsing for Windows currently, use the instructions found here:" & vbNewLine & _
697
+ vbNewLine & _
698
+ "https://github.com/VBA-tools/VBA-Web/wiki/XML-Support-in-4.0"
699
+
700
+ LogError web_ErrorMsg, "WebHelpers.ParseXml", 11099
701
+ Err.Raise 11099, "WebHeleprs.ParseXml", web_ErrorMsg
702
+ End Function
703
+
704
+ ''
705
+ ' Convert `Dictionary` to XML string.
706
+ '
707
+ ' _Note_ Currently, XML is not supported in 4.0.0 due to lack of Mac support.
708
+ ' An updated parser is being created that supports Mac and Windows,
709
+ ' but in order to avoid future breaking changes, ParseXml and ConvertToXml are not currently implemented.
710
+ '
711
+ ' See https://github.com/VBA-tools/VBA-Web/wiki/XML-Support-in-4.0 for details on how to use XML in Windows in the meantime.
712
+ '
713
+ ' @param {Dictionary|Variant} XML
714
+ ' @return {String} XML string
715
+ ' @throws 11099 / 80042b5b / -2147210405 - XML format is not currently supported
716
+ ''
717
+ Public Function ConvertToXml(Obj As Variant) As String
718
+ Dim web_ErrorMsg As String
719
+
720
+ web_ErrorMsg = "XML is not currently supported (An updated parser is being created that supports Mac and Windows)." & vbNewLine & _
721
+ "To use XML parsing for Windows currently, use the instructions found here:" & vbNewLine & _
722
+ vbNewLine & _
723
+ "https://github.com/VBA-tools/VBA-Web/wiki/XML-Support-in-4.0"
724
+
725
+ LogError web_ErrorMsg, "WebHelpers.ParseXml", 11099 + vbObjectError
726
+ Err.Raise 11099 + vbObjectError, "WebHeleprs.ParseXml", web_ErrorMsg
727
+ End Function
728
+
729
+ ''
730
+ ' Helper for parsing value to given `WebFormat` or custom format.
731
+ ' Returns `Dictionary` or `Collection` based on given `Value`.
732
+ '
733
+ ' @method ParseByFormat
734
+ ' @param {String} Value Value to parse
735
+ ' @param {WebFormat} Format
736
+ ' @param {String} [CustomFormat=""] Name of registered custom converter
737
+ ' @param {Variant} [Bytes] Bytes for custom convert (if `ParseType = "Binary"`)
738
+ ' @return {Dictionary|Collection|Object}
739
+ ' @throws 11000 - Error during parsing
740
+ ''
741
+ Public Function ParseByFormat(Value As String, Format As WebFormat, _
742
+ Optional CustomFormat As String = "", Optional Bytes As Variant) As Object
743
+
744
+ On Error GoTo web_ErrorHandling
745
+
746
+ ' Don't attempt to parse blank values
747
+ If Value = "" And CustomFormat = "" Then
748
+ Exit Function
749
+ End If
750
+
751
+ Select Case Format
752
+ Case WebFormat.Json
753
+ Set ParseByFormat = ParseJson(Value)
754
+ Case WebFormat.FormUrlEncoded
755
+ Set ParseByFormat = ParseUrlEncoded(Value)
756
+ Case WebFormat.Xml
757
+ Set ParseByFormat = ParseXml(Value)
758
+ Case WebFormat.Custom
759
+ #If EnableCustomFormatting Then
760
+ Dim web_Converter As Dictionary
761
+ Dim web_Callback As String
762
+
763
+ Set web_Converter = web_GetConverter(CustomFormat)
764
+ web_Callback = web_Converter("ParseCallback")
765
+
766
+ If web_Converter.Exists("Instance") Then
767
+ Dim web_Instance As Object
768
+ Set web_Instance = web_Converter("Instance")
769
+
770
+ If web_Converter("ParseType") = "Binary" Then
771
+ Set ParseByFormat = VBA.CallByName(web_Instance, web_Callback, VBA.VbMethod, Bytes)
772
+ Else
773
+ Set ParseByFormat = VBA.CallByName(web_Instance, web_Callback, VBA.VbMethod, Value)
774
+ End If
775
+ Else
776
+ If web_Converter("ParseType") = "Binary" Then
777
+ Set ParseByFormat = Application.Run(web_Callback, Bytes)
778
+ Else
779
+ Set ParseByFormat = Application.Run(web_Callback, Value)
780
+ End If
781
+ End If
782
+ #Else
783
+ LogWarning "Custom formatting is disabled. To use WebFormat.Custom, enable custom formatting with the EnableCustomFormatting flag in WebHelpers"
784
+ #End If
785
+ End Select
786
+ Exit Function
787
+
788
+ web_ErrorHandling:
789
+
790
+ Dim web_ErrorDescription As String
791
+ web_ErrorDescription = "An error occurred during parsing" & vbNewLine & _
792
+ Err.Number & VBA.IIf(Err.Number < 0, " (" & VBA.LCase$(VBA.Hex$(Err.Number)) & ")", "") & ": " & Err.Description
793
+
794
+ LogError web_ErrorDescription, "WebHelpers.ParseByFormat", 11000
795
+ Err.Raise 11000, "WebHelpers.ParseByFormat", web_ErrorDescription
796
+ End Function
797
+
798
+ ''
799
+ ' Helper for converting value to given `WebFormat` or custom format.
800
+ '
801
+ ' _Note_ Only some converters handle `Collection` or `Array`.
802
+ '
803
+ ' @method ConvertToFormat
804
+ ' @param {Dictionary|Collection|Variant} Obj
805
+ ' @param {WebFormat} Format
806
+ ' @param {String} [CustomFormat] Name of registered custom converter
807
+ ' @return {Variant}
808
+ ' @throws 11001 - Error during conversion
809
+ ''
810
+ Public Function ConvertToFormat(Obj As Variant, Format As WebFormat, Optional CustomFormat As String = "") As Variant
811
+ On Error GoTo web_ErrorHandling
812
+
813
+ Select Case Format
814
+ Case WebFormat.Json
815
+ ConvertToFormat = ConvertToJson(Obj)
816
+ Case WebFormat.FormUrlEncoded
817
+ ConvertToFormat = ConvertToUrlEncoded(Obj)
818
+ Case WebFormat.Xml
819
+ ConvertToFormat = ConvertToXml(Obj)
820
+ Case WebFormat.Custom
821
+ #If EnableCustomFormatting Then
822
+ Dim web_Converter As Dictionary
823
+ Dim web_Callback As String
824
+
825
+ Set web_Converter = web_GetConverter(CustomFormat)
826
+ web_Callback = web_Converter("ConvertCallback")
827
+
828
+ If web_Converter.Exists("Instance") Then
829
+ Dim web_Instance As Object
830
+ Set web_Instance = web_Converter("Instance")
831
+ ConvertToFormat = VBA.CallByName(web_Instance, web_Callback, VBA.VbMethod, Obj)
832
+ Else
833
+ ConvertToFormat = Application.Run(web_Callback, Obj)
834
+ End If
835
+ #Else
836
+ LogWarning "Custom formatting is disabled. To use WebFormat.Custom, enable custom formatting with the EnableCustomFormatting flag in WebHelpers"
837
+ #End If
838
+ Case Else
839
+ If VBA.VarType(Obj) = vbString Then
840
+ ' Plain text
841
+ ConvertToFormat = Obj
842
+ End If
843
+ End Select
844
+ Exit Function
845
+
846
+ web_ErrorHandling:
847
+
848
+ Dim web_ErrorDescription As String
849
+ web_ErrorDescription = "An error occurred during conversion" & vbNewLine & _
850
+ Err.Number & VBA.IIf(Err.Number < 0, " (" & VBA.LCase$(VBA.Hex$(Err.Number)) & ")", "") & ": " & Err.Description
851
+
852
+ LogError web_ErrorDescription, "WebHelpers.ConvertToFormat", 11001
853
+ Err.Raise 11001, "WebHelpers.ConvertToFormat", web_ErrorDescription
854
+ End Function
855
+
856
+ ''
857
+ ' Encode string for URLs
858
+ '
859
+ ' See https://github.com/VBA-tools/VBA-Web/wiki/Url-Encoding for details
860
+ '
861
+ ' References:
862
+ ' - RFC 3986, https://tools.ietf.org/html/rfc3986
863
+ ' - form-urlencoded encoding algorithm,
864
+ ' https://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm
865
+ ' - RFC 6265 (Cookies), https://tools.ietf.org/html/rfc6265
866
+ ' Note: "%" is allowed in spec, but is currently excluded due to parsing issues
867
+ '
868
+ ' @method UrlEncode
869
+ ' @param {Variant} Text Text to encode
870
+ ' @param {Boolean} [SpaceAsPlus = False] `%20` if `False` / `+` if `True`
871
+ ' DEPRECATED Use EncodingMode:=FormUrlEncoding
872
+ ' @param {Boolean} [EncodeUnsafe = True] Encode characters that could be misunderstood within URLs.
873
+ ' (``SPACE, ", <, >, #, %, {, }, |, \, ^, ~, `, [, ]``)
874
+ ' DEPRECATED This was based on an outdated URI spec and has since been removed.
875
+ ' EncodingMode:=CookieUrlEncoding is the closest approximation of this behavior
876
+ ' @param {UrlEncodingMode} [EncodingMode = StrictUrlEncoding]
877
+ ' @return {String} Encoded string
878
+ ''
879
+ Public Function UrlEncode(Text As Variant, _
880
+ Optional SpaceAsPlus As Boolean = False, Optional EncodeUnsafe As Boolean = True, _
881
+ Optional EncodingMode As UrlEncodingMode = UrlEncodingMode.StrictUrlEncoding) As String
882
+
883
+ If SpaceAsPlus = True Then
884
+ LogWarning "SpaceAsPlus is deprecated and will be removed in VBA-Web v5. " & _
885
+ "Use EncodingMode:=FormUrlEncoding instead", "WebHelpers.UrlEncode"
886
+ End If
887
+ If EncodeUnsafe = False Then
888
+ LogWarning "EncodeUnsafe has been removed as it was based on an outdated url encoding specification. " & _
889
+ "Use EncodingMode:=CookieUrlEncoding to approximate this behavior", "WebHelpers.UrlEncode"
890
+ End If
891
+
892
+ Dim web_UrlVal As String
893
+ Dim web_StringLen As Long
894
+
895
+ web_UrlVal = VBA.CStr(Text)
896
+ web_StringLen = VBA.Len(web_UrlVal)
897
+
898
+ If web_StringLen > 0 Then
899
+ Dim web_Result() As String
900
+ Dim web_i As Long
901
+ Dim web_CharCode As Integer
902
+ Dim web_Char As String
903
+ Dim web_Space As String
904
+ ReDim web_Result(web_StringLen)
905
+
906
+ ' StrictUrlEncoding - ALPHA / DIGIT / "-" / "." / "_" / "~"
907
+ ' FormUrlEncoding - ALPHA / DIGIT / "-" / "." / "_" / "*" / (space) -> "+"
908
+ ' QueryUrlEncoding - ALPHA / DIGIT / "-" / "." / "_"
909
+ ' CookieUrlEncoding - strict / "!" / "#" / "$" / "&" / "'" / "(" / ")" / "*" / "+" /
910
+ ' "/" / ":" / "<" / "=" / ">" / "?" / "@" / "[" / "]" / "^" / "`" / "{" / "|" / "}"
911
+ ' PathUrlEncoding - strict / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" / ":" / "@"
912
+
913
+ ' Set space value
914
+ If SpaceAsPlus Or EncodingMode = UrlEncodingMode.FormUrlEncoding Then
915
+ web_Space = "+"
916
+ Else
917
+ web_Space = "%20"
918
+ End If
919
+
920
+ ' Loop through string characters
921
+ For web_i = 1 To web_StringLen
922
+ ' Get character and ascii code
923
+ web_Char = VBA.Mid$(web_UrlVal, web_i, 1)
924
+ web_CharCode = VBA.Asc(web_Char)
925
+
926
+ Select Case web_CharCode
927
+ Case 65 To 90, 97 To 122
928
+ ' ALPHA
929
+ web_Result(web_i) = web_Char
930
+ Case 48 To 57
931
+ ' DIGIT
932
+ web_Result(web_i) = web_Char
933
+ Case 45, 46, 95
934
+ ' "-" / "." / "_"
935
+ web_Result(web_i) = web_Char
936
+
937
+ Case 32
938
+ ' (space)
939
+ ' FormUrlEncoding -> "+"
940
+ ' Else -> "%20"
941
+ web_Result(web_i) = web_Space
942
+
943
+ Case 33, 36, 38, 39, 40, 41, 43, 58, 61, 64
944
+ ' "!" / "$" / "&" / "'" / "(" / ")" / "+" / ":" / "=" / "@"
945
+ ' PathUrlEncoding, CookieUrlEncoding -> Unencoded
946
+ ' Else -> Percent-encoded
947
+ If EncodingMode = UrlEncodingMode.PathUrlEncoding Or EncodingMode = UrlEncodingMode.CookieUrlEncoding Then
948
+ web_Result(web_i) = web_Char
949
+ Else
950
+ web_Result(web_i) = "%" & VBA.Hex(web_CharCode)
951
+ End If
952
+
953
+ Case 35, 45, 46, 47, 60, 62, 63, 91, 93, 94, 95, 96, 123, 124, 125
954
+ ' "#" / "-" / "." / "/" / "<" / ">" / "?" / "[" / "]" / "^" / "_" / "`" / "{" / "|" / "}"
955
+ ' CookieUrlEncoding -> Unencoded
956
+ ' Else -> Percent-encoded
957
+ If EncodingMode = UrlEncodingMode.CookieUrlEncoding Then
958
+ web_Result(web_i) = web_Char
959
+ Else
960
+ web_Result(web_i) = "%" & VBA.Hex(web_CharCode)
961
+ End If
962
+
963
+ Case 42
964
+ ' "*"
965
+ ' FormUrlEncoding, PathUrlEncoding, CookieUrlEncoding -> "*"
966
+ ' Else -> "%2A"
967
+ If EncodingMode = UrlEncodingMode.FormUrlEncoding _
968
+ Or EncodingMode = UrlEncodingMode.PathUrlEncoding _
969
+ Or EncodingMode = UrlEncodingMode.CookieUrlEncoding Then
970
+
971
+ web_Result(web_i) = web_Char
972
+ Else
973
+ web_Result(web_i) = "%" & VBA.Hex(web_CharCode)
974
+ End If
975
+
976
+ Case 44, 59
977
+ ' "," / ";"
978
+ ' PathUrlEncoding -> Unencoded
979
+ ' Else -> Percent-encoded
980
+ If EncodingMode = UrlEncodingMode.PathUrlEncoding Then
981
+ web_Result(web_i) = web_Char
982
+ Else
983
+ web_Result(web_i) = "%" & VBA.Hex(web_CharCode)
984
+ End If
985
+
986
+ Case 126
987
+ ' "~"
988
+ ' FormUrlEncoding, QueryUrlEncoding -> "%7E"
989
+ ' Else -> "~"
990
+ If EncodingMode = UrlEncodingMode.FormUrlEncoding Or EncodingMode = UrlEncodingMode.QueryUrlEncoding Then
991
+ web_Result(web_i) = "%7E"
992
+ Else
993
+ web_Result(web_i) = web_Char
994
+ End If
995
+
996
+ Case 0 To 15
997
+ web_Result(web_i) = "%0" & VBA.Hex(web_CharCode)
998
+ Case Else
999
+ web_Result(web_i) = "%" & VBA.Hex(web_CharCode)
1000
+
1001
+ ' TODO For non-ASCII characters,
1002
+ '
1003
+ ' FormUrlEncoded:
1004
+ '
1005
+ ' Replace the character by a string consisting of a U+0026 AMPERSAND character (&), a "#" (U+0023) character,
1006
+ ' one or more ASCII digits representing the Unicode code point of the character in base ten, and finally a ";" (U+003B) character.
1007
+ '
1008
+ ' Else:
1009
+ '
1010
+ ' Encode to sequence of 2 or 3 bytes in UTF-8, then percent encode
1011
+ ' Reference Implementation: https://www.w3.org/International/URLUTF8Encoder.java
1012
+ End Select
1013
+ Next web_i
1014
+ UrlEncode = VBA.Join$(web_Result, "")
1015
+ End If
1016
+ End Function
1017
+
1018
+ ''
1019
+ ' Decode Url-encoded string.
1020
+ '
1021
+ ' @method UrlDecode
1022
+ ' @param {String} Encoded Text to decode
1023
+ ' @param {Boolean} [PlusAsSpace = True] Decode plus as space
1024
+ ' DEPRECATED Use EncodingMode:=FormUrlEncoding Or QueryUrlEncoding
1025
+ ' @param {UrlEncodingMode} [EncodingMode = StrictUrlEncoding]
1026
+ ' @return {String} Decoded string
1027
+ ''
1028
+ Public Function UrlDecode(Encoded As String, _
1029
+ Optional PlusAsSpace As Boolean = True, _
1030
+ Optional EncodingMode As UrlEncodingMode = UrlEncodingMode.StrictUrlEncoding) As String
1031
+
1032
+ Dim web_StringLen As Long
1033
+ web_StringLen = VBA.Len(Encoded)
1034
+
1035
+ If web_StringLen > 0 Then
1036
+ Dim web_i As Long
1037
+ Dim web_Result As String
1038
+ Dim web_Temp As String
1039
+
1040
+ For web_i = 1 To web_StringLen
1041
+ web_Temp = VBA.Mid$(Encoded, web_i, 1)
1042
+
1043
+ If web_Temp = "+" And _
1044
+ (PlusAsSpace _
1045
+ Or EncodingMode = UrlEncodingMode.FormUrlEncoding _
1046
+ Or EncodingMode = UrlEncodingMode.QueryUrlEncoding) Then
1047
+
1048
+ web_Temp = " "
1049
+ ElseIf web_Temp = "%" And web_StringLen >= web_i + 2 Then
1050
+ web_Temp = VBA.Mid$(Encoded, web_i + 1, 2)
1051
+ web_Temp = VBA.Chr(VBA.CInt("&H" & web_Temp))
1052
+
1053
+ web_i = web_i + 2
1054
+ End If
1055
+
1056
+ ' TODO Handle non-ASCII characters
1057
+
1058
+ web_Result = web_Result & web_Temp
1059
+ Next web_i
1060
+
1061
+ UrlDecode = web_Result
1062
+ End If
1063
+ End Function
1064
+
1065
+ ''
1066
+ ' Base64-encode text.
1067
+ '
1068
+ ' @param {Variant} Text Text to encode
1069
+ ' @return {String} Encoded string
1070
+ ''
1071
+ Public Function Base64Encode(Text As String) As String
1072
+ #If Mac Then
1073
+ Dim web_Command As String
1074
+ web_Command = "printf " & PrepareTextForPrintf(Text) & " | openssl base64"
1075
+ Base64Encode = ExecuteInShell(web_Command).Output
1076
+ #Else
1077
+ Dim web_Bytes() As Byte
1078
+
1079
+ web_Bytes = VBA.StrConv(Text, vbFromUnicode)
1080
+ Base64Encode = web_AnsiBytesToBase64(web_Bytes)
1081
+ #End If
1082
+
1083
+ Base64Encode = VBA.Replace$(Base64Encode, vbLf, "")
1084
+ End Function
1085
+
1086
+ ''
1087
+ ' Decode Base64-encoded text
1088
+ '
1089
+ ' @param {Variant} Encoded Text to decode
1090
+ ' @return {String} Decoded string
1091
+ ''
1092
+ Public Function Base64Decode(Encoded As Variant) As String
1093
+ ' Add trailing padding, if necessary
1094
+ If (VBA.Len(Encoded) Mod 4 > 0) Then
1095
+ Encoded = Encoded & VBA.Left("====", 4 - (VBA.Len(Encoded) Mod 4))
1096
+ End If
1097
+
1098
+ #If Mac Then
1099
+ Dim web_Command As String
1100
+ web_Command = "echo " & PrepareTextForShell(Encoded) & " | openssl base64 -d"
1101
+ Base64Decode = ExecuteInShell(web_Command).Output
1102
+ #Else
1103
+ Dim web_XmlObj As Object
1104
+ Dim web_Node As Object
1105
+
1106
+ Set web_XmlObj = CreateObject("MSXML2.DOMDocument")
1107
+ Set web_Node = web_XmlObj.createElement("b64")
1108
+
1109
+ web_Node.DataType = "bin.base64"
1110
+ web_Node.Text = Encoded
1111
+ Base64Decode = VBA.StrConv(web_Node.nodeTypedValue, vbUnicode)
1112
+
1113
+ Set web_Node = Nothing
1114
+ Set web_XmlObj = Nothing
1115
+ #End If
1116
+ End Function
1117
+
1118
+ ''
1119
+ ' Register custom converter for converting request `Body` and response `Content`.
1120
+ ' If the `ConvertCallback` or `ParseCallback` are object methods,
1121
+ ' pass in an object instance.
1122
+ ' If the `ParseCallback` needs the raw binary response value (e.g. file download),
1123
+ ' set `ParseType = "Binary"`, otherwise `"String"` is used.
1124
+ '
1125
+ ' - `ConvertCallback` signature: `Function ...(Value As Variant) As String`
1126
+ ' - `ParseCallback` signature: `Function ...(Value As String) As Object`
1127
+ '
1128
+ ' @example
1129
+ ' ```VB.net
1130
+ ' ' 1. Use global module functions for Convert and Parse
1131
+ ' ' ---
1132
+ ' ' Module: CSVConverter
1133
+ ' Function ParseCSV(Value As String) As Object
1134
+ ' ' ...
1135
+ ' End Function
1136
+ ' Function ConvertToCSV(Value As Variant) As String
1137
+ ' ' ...
1138
+ ' End Function
1139
+ '
1140
+ ' WebHelpers.RegisterConverter "csv", "text/csv", _
1141
+ ' "CSVConverter.ConvertToCSV", "CSVConverter.ParseCSV"
1142
+ '
1143
+ ' ' 2. Use object instance functions for Convert and Parse
1144
+ ' ' ---
1145
+ ' ' Object: CSVConverterClass
1146
+ ' ' same as above...
1147
+ '
1148
+ ' Dim Converter As New CSVConverterClass
1149
+ ' WebHelpers.RegisterConverter "csv", "text/csv", _
1150
+ ' "ConvertToCSV", "ParseCSV", Instance:=Converter
1151
+ '
1152
+ ' ' 3. Pass raw binary value to ParseCallback
1153
+ ' ' ---
1154
+ ' ' Module: ImageConverter
1155
+ ' Function ParseImage(Bytes As Variant) As Object
1156
+ ' ' ...
1157
+ ' End Function
1158
+ ' Function ConvertToImage(Value As Variant) As String
1159
+ ' ' ...
1160
+ ' End Function
1161
+ '
1162
+ ' WebHelpers.RegisterConverter "image", "image/jpeg", _
1163
+ ' "ImageConverter.ConvertToImage", "ImageConverter.ParseImage", _
1164
+ ' ParseType:="Binary"
1165
+ ' ```
1166
+ '
1167
+ ' @method RegisterConverter
1168
+ ' @param {String} Name
1169
+ ' Name of converter for use with `CustomRequestFormat` or `CustomResponseFormat`
1170
+ ' @param {String} MediaType
1171
+ ' Media type to use for `Content-Type` and `Accept` headers
1172
+ ' @param {String} ConvertCallback Global or object function name for converting
1173
+ ' @param {String} ParseCallback Global or object function name for parsing
1174
+ ' @param {Object} [Instance]
1175
+ ' Use instance methods for `ConvertCallback` and `ParseCallback`
1176
+ ' @param {String} [ParseType="String"]
1177
+ ' "String"` (default) or `"Binary"` to pass raw binary response to `ParseCallback`
1178
+ ''
1179
+ Public Sub RegisterConverter( _
1180
+ Name As String, MediaType As String, ConvertCallback As String, ParseCallback As String, _
1181
+ Optional Instance As Object, Optional ParseType As String = "String")
1182
+
1183
+ Dim web_Converter As New Dictionary
1184
+ web_Converter("MediaType") = MediaType
1185
+ web_Converter("ConvertCallback") = ConvertCallback
1186
+ web_Converter("ParseCallback") = ParseCallback
1187
+ web_Converter("ParseType") = ParseType
1188
+
1189
+ If Not Instance Is Nothing Then
1190
+ Set web_Converter("Instance") = Instance
1191
+ End If
1192
+
1193
+ If web_pConverters Is Nothing Then: Set web_pConverters = New Dictionary
1194
+ Set web_pConverters(Name) = web_Converter
1195
+ End Sub
1196
+
1197
+ ' Helper for getting custom converter
1198
+ ' @throws 11002 - No matching converter has been registered
1199
+ Private Function web_GetConverter(web_CustomFormat As String) As Dictionary
1200
+ If web_pConverters.Exists(web_CustomFormat) Then
1201
+ Set web_GetConverter = web_pConverters(web_CustomFormat)
1202
+ Else
1203
+ LogError "No matching converter has been registered for custom format: " & web_CustomFormat, _
1204
+ "WebHelpers.web_GetConverter", 11002
1205
+ Err.Raise 11002, "WebHelpers.web_GetConverter", _
1206
+ "No matching converter has been registered for custom format: " & web_CustomFormat
1207
+ End If
1208
+ End Function
1209
+
1210
+ ' ============================================= '
1211
+ ' 3. Url handling
1212
+ ' ============================================= '
1213
+
1214
+ ''
1215
+ ' Join Url with /
1216
+ '
1217
+ ' @example
1218
+ ' ```VB.net
1219
+ ' Debug.Print WebHelpers.JoinUrl("a/", "/b")
1220
+ ' Debug.Print WebHelpers.JoinUrl("a", "b")
1221
+ ' Debug.Print WebHelpers.JoinUrl("a/", "b")
1222
+ ' Debug.Print WebHelpers.JoinUrl("a", "/b")
1223
+ ' -> a/b
1224
+ ' ```
1225
+ '
1226
+ ' @param {String} LeftSide
1227
+ ' @param {String} RightSide
1228
+ ' @return {String} Joined url
1229
+ ''
1230
+ Public Function JoinUrl(LeftSide As String, RightSide As String) As String
1231
+ If Left(RightSide, 1) = "/" Then
1232
+ RightSide = Right(RightSide, Len(RightSide) - 1)
1233
+ End If
1234
+ If Right(LeftSide, 1) = "/" Then
1235
+ LeftSide = Left(LeftSide, Len(LeftSide) - 1)
1236
+ End If
1237
+
1238
+ If LeftSide <> "" And RightSide <> "" Then
1239
+ JoinUrl = LeftSide & "/" & RightSide
1240
+ Else
1241
+ JoinUrl = LeftSide & RightSide
1242
+ End If
1243
+ End Function
1244
+
1245
+ ''
1246
+ ' Get relevant parts of the given url.
1247
+ ' Returns `Protocol`, `Host`, `Port`, `Path`, `Querystring`, and `Hash`
1248
+ '
1249
+ ' @example
1250
+ ' ```VB.net
1251
+ ' WebHelpers.GetUrlParts "https://www.google.com/a/b/c.html?a=1&b=2#hash"
1252
+ ' ' -> Protocol = "https"
1253
+ ' ' Host = "www.google.com"
1254
+ ' ' Port = "443"
1255
+ ' ' Path = "/a/b/c.html"
1256
+ ' ' Querystring = "a=1&b=2"
1257
+ ' ' Hash = "hash"
1258
+ '
1259
+ ' WebHelpers.GetUrlParts "localhost:3000/a/b/c"
1260
+ ' ' -> Protocol = ""
1261
+ ' ' Host = "localhost"
1262
+ ' ' Port = "3000"
1263
+ ' ' Path = "/a/b/c"
1264
+ ' ' Querystring = ""
1265
+ ' ' Hash = ""
1266
+ ' ```
1267
+ '
1268
+ ' @method GetUrlParts
1269
+ ' @param {String} Url
1270
+ ' @return {Dictionary} Parts of url
1271
+ ' Protocol, Host, Port, Path, Querystring, Hash
1272
+ ' @throws 11003 - Error while getting url parts
1273
+ ''
1274
+ Public Function GetUrlParts(url As String) As Dictionary
1275
+ Dim web_Parts As New Dictionary
1276
+
1277
+ On Error GoTo web_ErrorHandling
1278
+
1279
+ #If Mac Then
1280
+ ' Run perl script to parse url
1281
+
1282
+ Dim web_AddedProtocol As Boolean
1283
+ Dim web_Command As String
1284
+ Dim web_Results As Variant
1285
+ Dim web_ResultPart As Variant
1286
+ Dim web_EqualsIndex As Long
1287
+ Dim web_Key As String
1288
+ Dim web_Value As String
1289
+
1290
+ ' Add Protocol if missing
1291
+ If InStr(1, url, "://") <= 0 Then
1292
+ web_AddedProtocol = True
1293
+ If InStr(1, url, "//") = 1 Then
1294
+ url = "http" & url
1295
+ Else
1296
+ url = "http://" & url
1297
+ End If
1298
+ End If
1299
+
1300
+ web_Command = "perl -e '{use URI::URL;" & vbNewLine & _
1301
+ "$url = new URI::URL """ & url & """;" & vbNewLine & _
1302
+ "print ""Protocol="" . $url->scheme;" & vbNewLine & _
1303
+ "print "" | Host="" . $url->host;" & vbNewLine & _
1304
+ "print "" | Port="" . $url->port;" & vbNewLine & _
1305
+ "print "" | FullPath="" . $url->full_path;" & vbNewLine & _
1306
+ "print "" | Hash="" . $url->frag;" & vbNewLine & _
1307
+ "}'"
1308
+
1309
+ web_Results = Split(ExecuteInShell(web_Command).Output, " | ")
1310
+ For Each web_ResultPart In web_Results
1311
+ web_EqualsIndex = InStr(1, web_ResultPart, "=")
1312
+ web_Key = Trim(VBA.Mid$(web_ResultPart, 1, web_EqualsIndex - 1))
1313
+ web_Value = Trim(VBA.Mid$(web_ResultPart, web_EqualsIndex + 1))
1314
+
1315
+ If web_Key = "FullPath" Then
1316
+ ' For properly escaped path and querystring, need to use full_path
1317
+ ' But, need to split FullPath into Path...?Querystring
1318
+ Dim QueryIndex As Integer
1319
+
1320
+ QueryIndex = InStr(1, web_Value, "?")
1321
+ If QueryIndex > 0 Then
1322
+ web_Parts.Add "Path", Mid$(web_Value, 1, QueryIndex - 1)
1323
+ web_Parts.Add "Querystring", Mid$(web_Value, QueryIndex + 1)
1324
+ Else
1325
+ web_Parts.Add "Path", web_Value
1326
+ web_Parts.Add "Querystring", ""
1327
+ End If
1328
+ Else
1329
+ web_Parts.Add web_Key, web_Value
1330
+ End If
1331
+ Next web_ResultPart
1332
+
1333
+ If web_AddedProtocol And web_Parts.Exists("Protocol") Then
1334
+ web_Parts("Protocol") = ""
1335
+ End If
1336
+ #Else
1337
+ ' Create document/element is expensive, cache after creation
1338
+ If web_pDocumentHelper Is Nothing Or web_pElHelper Is Nothing Then
1339
+ Set web_pDocumentHelper = CreateObject("htmlfile")
1340
+ Set web_pElHelper = web_pDocumentHelper.createElement("a")
1341
+ End If
1342
+
1343
+ web_pElHelper.href = url
1344
+ web_Parts.Add "Protocol", Replace(web_pElHelper.Protocol, ":", "", Count:=1)
1345
+ web_Parts.Add "Host", web_pElHelper.hostname
1346
+ web_Parts.Add "Port", web_pElHelper.port
1347
+ web_Parts.Add "Path", web_pElHelper.pathname
1348
+ web_Parts.Add "Querystring", Replace(web_pElHelper.Search, "?", "", Count:=1)
1349
+ web_Parts.Add "Hash", Replace(web_pElHelper.Hash, "#", "", Count:=1)
1350
+ #End If
1351
+
1352
+ If web_Parts("Protocol") = "localhost" Then
1353
+ ' localhost:port/... was passed in without protocol
1354
+ Dim PathParts As Variant
1355
+ PathParts = Split(web_Parts("Path"), "/")
1356
+
1357
+ web_Parts("Port") = PathParts(0)
1358
+ web_Parts("Protocol") = ""
1359
+ web_Parts("Host") = "localhost"
1360
+ web_Parts("Path") = Replace(web_Parts("Path"), web_Parts("Port"), "", Count:=1)
1361
+ End If
1362
+ If Left(web_Parts("Path"), 1) <> "/" Then
1363
+ web_Parts("Path") = "/" & web_Parts("Path")
1364
+ End If
1365
+
1366
+ Set GetUrlParts = web_Parts
1367
+ Exit Function
1368
+
1369
+ web_ErrorHandling:
1370
+
1371
+ Dim web_ErrorDescription As String
1372
+ web_ErrorDescription = "An error occurred while getting url parts" & vbNewLine & _
1373
+ Err.Number & VBA.IIf(Err.Number < 0, " (" & VBA.LCase$(VBA.Hex$(Err.Number)) & ")", "") & ": " & Err.Description
1374
+
1375
+ LogError web_ErrorDescription, "WebHelpers.GetUrlParts", 11003
1376
+ Err.Raise 11003, "WebHelpers.GetUrlParts", web_ErrorDescription
1377
+ End Function
1378
+
1379
+ ' ============================================= '
1380
+ ' 4. Object/Dictionary/Collection/Array helpers
1381
+ ' ============================================= '
1382
+
1383
+ ''
1384
+ ' Create a cloned copy of the `Dictionary`.
1385
+ ' This is not a deep copy, so children objects are copied by reference.
1386
+ '
1387
+ ' @method CloneDictionary
1388
+ ' @param {Dictionary} Original
1389
+ ' @return {Dictionary} Clone
1390
+ ''
1391
+ Public Function CloneDictionary(Original As Dictionary) As Dictionary
1392
+ Dim web_Key As Variant
1393
+
1394
+ Set CloneDictionary = New Dictionary
1395
+ For Each web_Key In Original.Keys
1396
+ CloneDictionary.Add VBA.CStr(web_Key), Original(web_Key)
1397
+ Next web_Key
1398
+ End Function
1399
+
1400
+ ''
1401
+ ' Create a cloned copy of the `Collection`.
1402
+ ' This is not a deep copy, so children objects are copied by reference.
1403
+ '
1404
+ ' _Note_ Keys are not transferred to clone
1405
+ '
1406
+ ' @method CloneCollection
1407
+ ' @param {Collection} Original
1408
+ ' @return {Collection} Clone
1409
+ ''
1410
+ Public Function CloneCollection(Original As Collection) As Collection
1411
+ Dim web_Item As Variant
1412
+
1413
+ Set CloneCollection = New Collection
1414
+ For Each web_Item In Original
1415
+ CloneCollection.Add web_Item
1416
+ Next web_Item
1417
+ End Function
1418
+
1419
+ ''
1420
+ ' Helper for creating `Key-Value` pair with `Dictionary`.
1421
+ ' Used in `WebRequest`/`WebResponse` `Cookies`, `Headers`, and `QuerystringParams`
1422
+ '
1423
+ ' @example
1424
+ ' ```VB.net
1425
+ ' WebHelpers.CreateKeyValue "abc", 123
1426
+ ' ' -> {"Key": "abc", "Value": 123}
1427
+ ' ```
1428
+ '
1429
+ ' @method CreateKeyValue
1430
+ ' @param {String} Key
1431
+ ' @param {Variant} Value
1432
+ ' @return {Dictionary}
1433
+ ''
1434
+ Public Function CreateKeyValue(Key As String, Value As Variant) As Dictionary
1435
+ Dim web_KeyValue As New Dictionary
1436
+
1437
+ web_KeyValue("Key") = Key
1438
+ web_KeyValue("Value") = Value
1439
+ Set CreateKeyValue = web_KeyValue
1440
+ End Function
1441
+
1442
+ ''
1443
+ ' Search a `Collection` of `KeyValue` and retrieve the value for the given key.
1444
+ '
1445
+ ' @example
1446
+ ' ```VB.net
1447
+ ' Dim KeyValues As New Collection
1448
+ ' KeyValues.Add WebHelpers.CreateKeyValue("abc", 123)
1449
+ '
1450
+ ' WebHelpers.FindInKeyValues KeyValues, "abc"
1451
+ ' ' -> 123
1452
+ '
1453
+ ' WebHelpers.FindInKeyValues KeyValues, "unknown"
1454
+ ' ' -> Empty
1455
+ ' ```
1456
+ '
1457
+ ' @method FindInKeyValues
1458
+ ' @param {Collection} KeyValues
1459
+ ' @param {Variant} Key to find
1460
+ ' @return {Variant}
1461
+ ''
1462
+ Public Function FindInKeyValues(KeyValues As Collection, Key As Variant) As Variant
1463
+ Dim web_KeyValue As Dictionary
1464
+
1465
+ For Each web_KeyValue In KeyValues
1466
+ If web_KeyValue("Key") = Key Then
1467
+ FindInKeyValues = web_KeyValue("Value")
1468
+ Exit Function
1469
+ End If
1470
+ Next web_KeyValue
1471
+ End Function
1472
+
1473
+ ''
1474
+ ' Helper for adding/replacing `KeyValue` in `Collection` of `KeyValue`
1475
+ ' - Add if key not found
1476
+ ' - Replace if key is found
1477
+ '
1478
+ ' @example
1479
+ ' ```VB.net
1480
+ ' Dim KeyValues As New Collection
1481
+ ' KeyValues.Add WebHelpers.CreateKeyValue("a", 123)
1482
+ ' KeyValues.Add WebHelpers.CreateKeyValue("b", 456)
1483
+ ' KeyValues.Add WebHelpers.CreateKeyValue("c", 789)
1484
+ '
1485
+ ' WebHelpers.AddOrReplaceInKeyValues KeyValues, "b", "abc"
1486
+ ' WebHelpers.AddOrReplaceInKeyValues KeyValues, "d", "def"
1487
+ '
1488
+ ' ' -> [
1489
+ ' ' {"Key":"a","Value":123},
1490
+ ' ' {"Key":"b","Value":"abc"},
1491
+ ' ' {"Key":"c","Value":789},
1492
+ ' ' {"Key":"d","Value":"def"}
1493
+ ' ' ]
1494
+ ' ```
1495
+ '
1496
+ ' @method AddOrReplaceInKeyValues
1497
+ ' @param {Collection} KeyValues
1498
+ ' @param {Variant} Key
1499
+ ' @param {Variant} Value
1500
+ ' @return {Variant}
1501
+ ''
1502
+ Public Sub AddOrReplaceInKeyValues(KeyValues As Collection, Key As Variant, Value As Variant)
1503
+ Dim web_KeyValue As Dictionary
1504
+ Dim web_Index As Long
1505
+ Dim web_NewKeyValue As Dictionary
1506
+
1507
+ Set web_NewKeyValue = CreateKeyValue(CStr(Key), Value)
1508
+
1509
+ web_Index = 1
1510
+ For Each web_KeyValue In KeyValues
1511
+ If web_KeyValue("Key") = Key Then
1512
+ ' Replace existing
1513
+ KeyValues.Remove web_Index
1514
+
1515
+ If KeyValues.Count = 0 Then
1516
+ KeyValues.Add web_NewKeyValue
1517
+ ElseIf web_Index > KeyValues.Count Then
1518
+ KeyValues.Add web_NewKeyValue, After:=web_Index - 1
1519
+ Else
1520
+ KeyValues.Add web_NewKeyValue, Before:=web_Index
1521
+ End If
1522
+ Exit Sub
1523
+ End If
1524
+
1525
+ web_Index = web_Index + 1
1526
+ Next web_KeyValue
1527
+
1528
+ ' Add
1529
+ KeyValues.Add web_NewKeyValue
1530
+ End Sub
1531
+
1532
+ ' ============================================= '
1533
+ ' 5. Request preparation / handling
1534
+ ' ============================================= '
1535
+
1536
+ ''
1537
+ ' Get the media-type for the given format / custom format.
1538
+ '
1539
+ ' @method FormatToMediaType
1540
+ ' @param {WebFormat} Format
1541
+ ' @param {String} [CustomFormat] Needed if `Format = WebFormat.Custom`
1542
+ ' @return {String}
1543
+ ''
1544
+ Public Function FormatToMediaType(Format As WebFormat, Optional CustomFormat As String) As String
1545
+ Select Case Format
1546
+ Case WebFormat.FormUrlEncoded
1547
+ FormatToMediaType = "application/x-www-form-urlencoded;charset=UTF-8"
1548
+ Case WebFormat.Json
1549
+ FormatToMediaType = "application/json"
1550
+ Case WebFormat.Xml
1551
+ FormatToMediaType = "application/xml"
1552
+ Case WebFormat.Custom
1553
+ FormatToMediaType = web_GetConverter(CustomFormat)("MediaType")
1554
+ Case Else
1555
+ FormatToMediaType = "text/plain"
1556
+ End Select
1557
+ End Function
1558
+
1559
+ ''
1560
+ ' Get the method name for the given `WebMethod`
1561
+ '
1562
+ ' @example
1563
+ ' ```VB.net
1564
+ ' WebHelpers.MethodToName WebMethod.HttpPost
1565
+ ' ' -> "POST"
1566
+ ' ```
1567
+ '
1568
+ ' @method MethodToName
1569
+ ' @param {WebMethod} Method
1570
+ ' @return {String}
1571
+ ''
1572
+ Public Function MethodToName(Method As WebMethod) As String
1573
+ Select Case Method
1574
+ Case WebMethod.HttpDelete
1575
+ MethodToName = "DELETE"
1576
+ Case WebMethod.HttpPut
1577
+ MethodToName = "PUT"
1578
+ Case WebMethod.HttpPatch
1579
+ MethodToName = "PATCH"
1580
+ Case WebMethod.HttpPost
1581
+ MethodToName = "POST"
1582
+ Case WebMethod.HttpGet
1583
+ MethodToName = "GET"
1584
+ Case WebMethod.HttpHead
1585
+ MethodToName = "HEAD"
1586
+ End Select
1587
+ End Function
1588
+
1589
+ ' ============================================= '
1590
+ ' 6. Timing
1591
+ ' ============================================= '
1592
+
1593
+ ''
1594
+ ' Handle timeout timers expiring
1595
+ '
1596
+ ' @internal
1597
+ ' @method OnTimeoutTimerExpired
1598
+ ' @param {String} RequestId
1599
+ ''
1600
+ Public Sub OnTimeoutTimerExpired(web_RequestId As String)
1601
+ If Not AsyncRequests Is Nothing Then
1602
+ If AsyncRequests.Exists(web_RequestId) Then
1603
+ Dim web_AsyncWrapper As Object
1604
+ Set web_AsyncWrapper = AsyncRequests(web_RequestId)
1605
+ web_AsyncWrapper.TimedOut
1606
+ End If
1607
+ End If
1608
+ End Sub
1609
+
1610
+ ' ============================================= '
1611
+ ' 7. Mac
1612
+ ' ============================================= '
1613
+
1614
+ ''
1615
+ ' Execute the given command
1616
+ '
1617
+ ' @internal
1618
+ ' @method ExecuteInShell
1619
+ ' @param {String} Command
1620
+ ' @return {ShellResult}
1621
+ ''
1622
+ Public Function ExecuteInShell(web_Command As String) As ShellResult
1623
+ #If Mac Then
1624
+ #If VBA7 Then
1625
+ Dim web_File As LongPtr
1626
+ #Else
1627
+ Dim web_File As Long
1628
+ #End If
1629
+
1630
+ Dim web_Chunk As String
1631
+ Dim web_Read As Long
1632
+
1633
+ On Error GoTo web_Cleanup
1634
+
1635
+ web_File = web_popen(web_Command, "r")
1636
+
1637
+ If web_File = 0 Then
1638
+ ' TODO Investigate why this could happen and what should be done if it happens
1639
+ Exit Function
1640
+ End If
1641
+
1642
+ Do While web_feof(web_File) = 0
1643
+ web_Chunk = VBA.Space$(50)
1644
+ web_Read = CLng(web_fread(web_Chunk, 1, Len(web_Chunk) - 1, web_File))
1645
+ If web_Read > 0 Then
1646
+ web_Chunk = VBA.Left$(web_Chunk, web_Read)
1647
+ ExecuteInShell.Output = ExecuteInShell.Output & web_Chunk
1648
+ End If
1649
+ Loop
1650
+
1651
+ web_Cleanup:
1652
+
1653
+ ExecuteInShell.ExitCode = CLng(web_pclose(web_File))
1654
+ #End If
1655
+ End Function
1656
+
1657
+ ''
1658
+ ' Prepare text for shell
1659
+ ' - Wrap in "..."
1660
+ ' - Replace ! with '!' (reserved in bash)
1661
+ ' - Escape \, `, $, %, and "
1662
+ '
1663
+ ' @internal
1664
+ ' @method PrepareTextForShell
1665
+ ' @param {String} Text
1666
+ ' @return {String}
1667
+ ''
1668
+ Public Function PrepareTextForShell(ByVal web_Text As String) As String
1669
+ ' Escape special characters (except for !)
1670
+ web_Text = VBA.Replace(web_Text, "\", "\\")
1671
+ web_Text = VBA.Replace(web_Text, "`", "\`")
1672
+ web_Text = VBA.Replace(web_Text, "$", "\$")
1673
+ web_Text = VBA.Replace(web_Text, "%", "\%")
1674
+ web_Text = VBA.Replace(web_Text, """", "\""")
1675
+
1676
+ ' Wrap in quotes
1677
+ web_Text = """" & web_Text & """"
1678
+
1679
+ ' Escape !
1680
+ web_Text = VBA.Replace(web_Text, "!", """'!'""")
1681
+
1682
+ ' Guard for ! at beginning or end (""'!'"..." or "..."'!'"" -> '!'"..." or "..."'!')
1683
+ If VBA.Left$(web_Text, 3) = """""'" Then
1684
+ web_Text = VBA.Right$(web_Text, VBA.Len(web_Text) - 2)
1685
+ End If
1686
+ If VBA.Right$(web_Text, 3) = "'""""" Then
1687
+ web_Text = VBA.Left$(web_Text, VBA.Len(web_Text) - 2)
1688
+ End If
1689
+
1690
+ PrepareTextForShell = web_Text
1691
+ End Function
1692
+
1693
+ ''
1694
+ ' Prepare text for using with printf command
1695
+ ' - Wrap in "..."
1696
+ ' - Replace ! with '!' (reserved in bash)
1697
+ ' - Escape \, `, $, and "
1698
+ ' - Replace % with %% (used as an argument marker in printf)
1699
+ '
1700
+ ' @internal
1701
+ ' @method PrepareTextForPrintf
1702
+ ' @param {String} Text
1703
+ ' @return {String}
1704
+ ''
1705
+ Public Function PrepareTextForPrintf(ByVal web_Text As String) As String
1706
+ ' Escape special characters (except for !)
1707
+ web_Text = VBA.Replace(web_Text, "\", "\\")
1708
+ web_Text = VBA.Replace(web_Text, "`", "\`")
1709
+ web_Text = VBA.Replace(web_Text, "$", "\$")
1710
+ web_Text = VBA.Replace(web_Text, "%", "%%")
1711
+ web_Text = VBA.Replace(web_Text, """", "\""")
1712
+
1713
+ ' Wrap in quotes
1714
+ web_Text = """" & web_Text & """"
1715
+
1716
+ ' Escape !
1717
+ web_Text = VBA.Replace(web_Text, "!", """'!'""")
1718
+
1719
+ ' Guard for ! at beginning or end (""'!'"..." or "..."'!'"" -> '!'"..." or "..."'!')
1720
+ If VBA.Left$(web_Text, 3) = """""'" Then
1721
+ web_Text = VBA.Right$(web_Text, VBA.Len(web_Text) - 2)
1722
+ End If
1723
+ If VBA.Right$(web_Text, 3) = "'""""" Then
1724
+ web_Text = VBA.Left$(web_Text, VBA.Len(web_Text) - 2)
1725
+ End If
1726
+
1727
+ PrepareTextForPrintf = web_Text
1728
+ End Function
1729
+
1730
+ ' ============================================= '
1731
+ ' 8. Cryptography
1732
+ ' ============================================= '
1733
+
1734
+ ''
1735
+ ' Determine the HMAC for the given text and secret using the SHA1 hash algorithm.
1736
+ '
1737
+ ' Reference:
1738
+ ' - http://stackoverflow.com/questions/8246340/does-vba-have-a-hash-hmac
1739
+ '
1740
+ ' @example
1741
+ ' ```VB.net
1742
+ ' WebHelpers.HMACSHA1 "Howdy!", "Secret"
1743
+ ' ' -> c8fdf74a9d62aa41ac8136a1af471cec028fb157
1744
+ ' ```
1745
+ '
1746
+ ' @method HMACSHA1
1747
+ ' @param {String} Text
1748
+ ' @param {String} Secret
1749
+ ' @param {String} [Format="Hex"] "Hex" or "Base64" encoding for result
1750
+ ' @return {String} HMAC-SHA1
1751
+ ''
1752
+ Public Function HMACSHA1(Text As String, Secret As String, Optional Format As String = "Hex") As String
1753
+ #If Mac Then
1754
+ Dim web_Command As String
1755
+ web_Command = "printf " & PrepareTextForPrintf(Text) & " | openssl dgst -sha1 -hmac " & PrepareTextForShell(Secret)
1756
+
1757
+ If Format = "Base64" Then
1758
+ web_Command = web_Command & " -binary | openssl enc -base64"
1759
+ End If
1760
+
1761
+ HMACSHA1 = VBA.Replace(ExecuteInShell(web_Command).Output, vbLf, "")
1762
+ #Else
1763
+ Dim web_Crypto As Object
1764
+ Dim web_TextBytes() As Byte
1765
+ Dim web_SecretBytes() As Byte
1766
+ Dim web_Bytes() As Byte
1767
+
1768
+ web_TextBytes = VBA.StrConv(Text, vbFromUnicode)
1769
+ web_SecretBytes = VBA.StrConv(Secret, vbFromUnicode)
1770
+
1771
+ Set web_Crypto = CreateObject("System.Security.Cryptography.HMACSHA1")
1772
+ web_Crypto.Key = web_SecretBytes
1773
+ web_Bytes = web_Crypto.ComputeHash_2(web_TextBytes)
1774
+
1775
+ Select Case Format
1776
+ Case "Base64"
1777
+ HMACSHA1 = web_AnsiBytesToBase64(web_Bytes)
1778
+ Case Else
1779
+ HMACSHA1 = web_AnsiBytesToHex(web_Bytes)
1780
+ End Select
1781
+ #End If
1782
+ End Function
1783
+
1784
+ ''
1785
+ ' Determine the HMAC for the given text and secret using the SHA256 hash algorithm.
1786
+ '
1787
+ ' @example
1788
+ ' ```VB.net
1789
+ ' WebHelpers.HMACSHA256 "Howdy!", "Secret"
1790
+ ' ' -> fb5d65...
1791
+ ' ```
1792
+ '
1793
+ ' @method HMACSHA256
1794
+ ' @param {String} Text
1795
+ ' @param {String} Secret
1796
+ ' @param {String} [Format="Hex"] "Hex" or "Base64" encoding for result
1797
+ ' @return {String} HMAC-SHA256
1798
+ ''
1799
+ Public Function HMACSHA256(Text As String, Secret As String, Optional Format As String = "Hex") As String
1800
+ #If Mac Then
1801
+ Dim web_Command As String
1802
+ web_Command = "printf " & PrepareTextForPrintf(Text) & " | openssl dgst -sha256 -hmac " & PrepareTextForShell(Secret)
1803
+
1804
+ If Format = "Base64" Then
1805
+ web_Command = web_Command & " -binary | openssl enc -base64"
1806
+ End If
1807
+
1808
+ HMACSHA256 = VBA.Replace(ExecuteInShell(web_Command).Output, vbLf, "")
1809
+ #Else
1810
+ Dim web_Crypto As Object
1811
+ Dim web_TextBytes() As Byte
1812
+ Dim web_SecretBytes() As Byte
1813
+ Dim web_Bytes() As Byte
1814
+
1815
+ web_TextBytes = VBA.StrConv(Text, vbFromUnicode)
1816
+ web_SecretBytes = VBA.StrConv(Secret, vbFromUnicode)
1817
+
1818
+ Set web_Crypto = CreateObject("System.Security.Cryptography.HMACSHA256")
1819
+ web_Crypto.Key = web_SecretBytes
1820
+ web_Bytes = web_Crypto.ComputeHash_2(web_TextBytes)
1821
+
1822
+ Select Case Format
1823
+ Case "Base64"
1824
+ HMACSHA256 = web_AnsiBytesToBase64(web_Bytes)
1825
+ Case Else
1826
+ HMACSHA256 = web_AnsiBytesToHex(web_Bytes)
1827
+ End Select
1828
+ #End If
1829
+ End Function
1830
+
1831
+ ''
1832
+ ' Determine the MD5 hash of the given text.
1833
+ '
1834
+ ' Reference:
1835
+ ' - http://www.di-mgt.com.au/src/basMD5.bas.html
1836
+ '
1837
+ ' @example
1838
+ ' ```VB.net
1839
+ ' WebHelpers.MD5 "Howdy!"
1840
+ ' ' -> 7105f32280940271293ee00ac97da5a7
1841
+ ' ```
1842
+ '
1843
+ ' @method MD5
1844
+ ' @param {String} Text
1845
+ ' @param {String} [Format="Hex"] "Hex" or "Base64" encoding for result
1846
+ ' @return {String} MD5 Hash
1847
+ ''
1848
+ Public Function MD5(Text As String, Optional Format As String = "Hex") As String
1849
+ #If Mac Then
1850
+ Dim web_Command As String
1851
+ web_Command = "printf " & PrepareTextForPrintf(Text) & " | openssl dgst -md5"
1852
+
1853
+ If Format = "Base64" Then
1854
+ web_Command = web_Command & " -binary | openssl enc -base64"
1855
+ End If
1856
+
1857
+ MD5 = VBA.Replace(ExecuteInShell(web_Command).Output, vbLf, "")
1858
+ #Else
1859
+ Dim web_Crypto As Object
1860
+ Dim web_TextBytes() As Byte
1861
+ Dim web_Bytes() As Byte
1862
+
1863
+ web_TextBytes = VBA.StrConv(Text, vbFromUnicode)
1864
+
1865
+ Set web_Crypto = CreateObject("System.Security.Cryptography.MD5CryptoServiceProvider")
1866
+ web_Bytes = web_Crypto.ComputeHash_2(web_TextBytes)
1867
+
1868
+ Select Case Format
1869
+ Case "Base64"
1870
+ MD5 = web_AnsiBytesToBase64(web_Bytes)
1871
+ Case Else
1872
+ MD5 = web_AnsiBytesToHex(web_Bytes)
1873
+ End Select
1874
+ #End If
1875
+ End Function
1876
+
1877
+ ''
1878
+ ' Create random alphanumeric nonce (0-9a-zA-Z)
1879
+ '
1880
+ ' @method CreateNonce
1881
+ ' @param {Integer} [NonceLength=32]
1882
+ ' @return {String} Randomly generated nonce
1883
+ ''
1884
+ Public Function CreateNonce(Optional NonceLength As Integer = 32) As String
1885
+ Dim web_Str As String
1886
+ Dim web_Count As Integer
1887
+ Dim web_Result As String
1888
+ Dim web_Random As Integer
1889
+
1890
+ web_Str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUIVWXYZ"
1891
+ web_Result = ""
1892
+
1893
+ VBA.Randomize
1894
+ For web_Count = 1 To NonceLength
1895
+ web_Random = VBA.Int(((VBA.Len(web_Str) - 1) * VBA.Rnd) + 1)
1896
+ web_Result = web_Result & VBA.Mid$(web_Str, web_Random, 1)
1897
+ Next
1898
+ CreateNonce = web_Result
1899
+ End Function
1900
+
1901
+ ''
1902
+ ' Convert string to ANSI bytes
1903
+ '
1904
+ ' @internal
1905
+ ' @method StringToAnsiBytes
1906
+ ' @param {String} Text
1907
+ ' @return {Byte()}
1908
+ ''
1909
+ Public Function StringToAnsiBytes(web_Text As String) As Byte()
1910
+ Dim web_Bytes() As Byte
1911
+ Dim web_AnsiBytes() As Byte
1912
+ Dim web_ByteIndex As Long
1913
+ Dim web_AnsiIndex As Long
1914
+
1915
+ If VBA.Len(web_Text) > 0 Then
1916
+ ' Take first byte from unicode bytes
1917
+ ' VBA.Int is used for floor instead of round
1918
+ web_Bytes = web_Text
1919
+ ReDim web_AnsiBytes(VBA.Int(UBound(web_Bytes) / 2))
1920
+
1921
+ web_AnsiIndex = LBound(web_Bytes)
1922
+ For web_ByteIndex = LBound(web_Bytes) To UBound(web_Bytes) Step 2
1923
+ web_AnsiBytes(web_AnsiIndex) = web_Bytes(web_ByteIndex)
1924
+ web_AnsiIndex = web_AnsiIndex + 1
1925
+ Next web_ByteIndex
1926
+ End If
1927
+
1928
+ StringToAnsiBytes = web_AnsiBytes
1929
+ End Function
1930
+
1931
+ #If Mac Then
1932
+ #Else
1933
+ Private Function web_AnsiBytesToBase64(web_Bytes() As Byte)
1934
+ ' Use XML to convert to Base64
1935
+ Dim web_XmlObj As Object
1936
+ Dim web_Node As Object
1937
+
1938
+ Set web_XmlObj = CreateObject("MSXML2.DOMDocument")
1939
+ Set web_Node = web_XmlObj.createElement("b64")
1940
+
1941
+ web_Node.DataType = "bin.base64"
1942
+ web_Node.nodeTypedValue = web_Bytes
1943
+ web_AnsiBytesToBase64 = web_Node.Text
1944
+
1945
+ Set web_Node = Nothing
1946
+ Set web_XmlObj = Nothing
1947
+ End Function
1948
+
1949
+ Private Function web_AnsiBytesToHex(web_Bytes() As Byte)
1950
+ Dim web_i As Long
1951
+ For web_i = LBound(web_Bytes) To UBound(web_Bytes)
1952
+ web_AnsiBytesToHex = web_AnsiBytesToHex & VBA.LCase$(VBA.Right$("0" & VBA.Hex$(web_Bytes(web_i)), 2))
1953
+ Next web_i
1954
+ End Function
1955
+ #End If
1956
+
1957
+ ' ============================================= '
1958
+ ' 9. Converters
1959
+ ' ============================================= '
1960
+
1961
+ ' Helper for url-encoded to create key=value pair
1962
+ Private Function web_GetUrlEncodedKeyValue(Key As Variant, Value As Variant, Optional EncodingMode As UrlEncodingMode = UrlEncodingMode.FormUrlEncoding) As String
1963
+ Select Case VBA.VarType(Value)
1964
+ Case VBA.vbBoolean
1965
+ ' Convert boolean to lowercase
1966
+ If Value Then
1967
+ Value = "true"
1968
+ Else
1969
+ Value = "false"
1970
+ End If
1971
+ Case VBA.vbDate
1972
+ ' Use region invariant date (ISO-8601)
1973
+ Value = WebHelpers.ConvertToIso(CDate(Value))
1974
+ Case VBA.vbDecimal, VBA.vbSingle, VBA.vbDouble, VBA.vbCurrency
1975
+ ' Use region invariant number encoding ("." for decimal separator)
1976
+ Value = VBA.Replace(VBA.CStr(Value), ",", ".")
1977
+ End Select
1978
+
1979
+ ' Url encode key and value (using + for spaces)
1980
+ web_GetUrlEncodedKeyValue = UrlEncode(Key, EncodingMode:=EncodingMode) & "=" & UrlEncode(Value, EncodingMode:=EncodingMode)
1981
+ End Function
1982
+
1983
+ ''
1984
+ ' VBA-JSON v2.3.1
1985
+ ' (c) Tim Hall - https://github.com/VBA-tools/VBA-JSON
1986
+ '
1987
+ ' JSON Converter for VBA
1988
+ '
1989
+ ' Errors:
1990
+ ' 10001 - JSON parse error
1991
+ '
1992
+ ' @class JsonConverter
1993
+ ' @author tim.hall.engr@gmail.com
1994
+ ' @license MIT (http://www.opensource.org/licenses/mit-license.php)
1995
+ '' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ '
1996
+ '
1997
+ ' Based originally on vba-json (with extensive changes)
1998
+ ' BSD license included below
1999
+ '
2000
+ ' JSONLib, http://code.google.com/p/vba-json/
2001
+ '
2002
+ ' Copyright (c) 2013, Ryo Yokoyama
2003
+ ' All rights reserved.
2004
+ '
2005
+ ' Redistribution and use in source and binary forms, with or without
2006
+ ' modification, are permitted provided that the following conditions are met:
2007
+ ' * Redistributions of source code must retain the above copyright
2008
+ ' notice, this list of conditions and the following disclaimer.
2009
+ ' * Redistributions in binary form must reproduce the above copyright
2010
+ ' notice, this list of conditions and the following disclaimer in the
2011
+ ' documentation and/or other materials provided with the distribution.
2012
+ ' * Neither the name of the <organization> nor the
2013
+ ' names of its contributors may be used to endorse or promote products
2014
+ ' derived from this software without specific prior written permission.
2015
+ '
2016
+ ' THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
2017
+ ' ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
2018
+ ' WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
2019
+ ' DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
2020
+ ' DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
2021
+ ' (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
2022
+ ' LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
2023
+ ' ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2024
+ ' (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
2025
+ ' SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2026
+ ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ '
2027
+
2028
+ ' (Declarations moved to top)
2029
+
2030
+ ' ============================================= '
2031
+ ' Public Methods
2032
+ ' ============================================= '
2033
+
2034
+ ''
2035
+ ' Convert JSON string to object (Dictionary/Collection)
2036
+ '
2037
+ ' @method ParseJson
2038
+ ' @param {String} json_String
2039
+ ' @return {Object} (Dictionary or Collection)
2040
+ ' @throws 10001 - JSON parse error
2041
+ ''
2042
+ Public Function ParseJson(ByVal JsonString As String) As Object
2043
+ Dim json_Index As Long
2044
+ json_Index = 1
2045
+
2046
+ ' Remove vbCr, vbLf, and vbTab from json_String
2047
+ JsonString = VBA.Replace(VBA.Replace(VBA.Replace(JsonString, VBA.vbCr, ""), VBA.vbLf, ""), VBA.vbTab, "")
2048
+
2049
+ json_SkipSpaces JsonString, json_Index
2050
+ Select Case VBA.Mid$(JsonString, json_Index, 1)
2051
+ Case "{"
2052
+ Set ParseJson = json_ParseObject(JsonString, json_Index)
2053
+ Case "["
2054
+ Set ParseJson = json_ParseArray(JsonString, json_Index)
2055
+ Case Else
2056
+ ' Error: Invalid JSON string
2057
+ Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(JsonString, json_Index, "Expecting '{' or '['")
2058
+ End Select
2059
+ End Function
2060
+
2061
+ ''
2062
+ ' Convert object (Dictionary/Collection/Array) to JSON
2063
+ '
2064
+ ' @method ConvertToJson
2065
+ ' @param {Variant} JsonValue (Dictionary, Collection, or Array)
2066
+ ' @param {Integer|String} Whitespace "Pretty" print json with given number of spaces per indentation (Integer) or given string
2067
+ ' @return {String}
2068
+ ''
2069
+ Public Function ConvertToJson(ByVal JsonValue As Variant, Optional ByVal Whitespace As Variant, Optional ByVal json_CurrentIndentation As Long = 0) As String
2070
+ Dim json_Buffer As String
2071
+ Dim json_BufferPosition As Long
2072
+ Dim json_BufferLength As Long
2073
+ Dim json_Index As Long
2074
+ Dim json_LBound As Long
2075
+ Dim json_UBound As Long
2076
+ Dim json_IsFirstItem As Boolean
2077
+ Dim json_Index2D As Long
2078
+ Dim json_LBound2D As Long
2079
+ Dim json_UBound2D As Long
2080
+ Dim json_IsFirstItem2D As Boolean
2081
+ Dim json_Key As Variant
2082
+ Dim json_Value As Variant
2083
+ Dim json_DateStr As String
2084
+ Dim json_Converted As String
2085
+ Dim json_SkipItem As Boolean
2086
+ Dim json_PrettyPrint As Boolean
2087
+ Dim json_Indentation As String
2088
+ Dim json_InnerIndentation As String
2089
+
2090
+ json_LBound = -1
2091
+ json_UBound = -1
2092
+ json_IsFirstItem = True
2093
+ json_LBound2D = -1
2094
+ json_UBound2D = -1
2095
+ json_IsFirstItem2D = True
2096
+ json_PrettyPrint = Not IsMissing(Whitespace)
2097
+
2098
+ Select Case VBA.VarType(JsonValue)
2099
+ Case VBA.vbNull
2100
+ ConvertToJson = "null"
2101
+ Case VBA.vbDate
2102
+ ' Date
2103
+ json_DateStr = ConvertToIso(VBA.CDate(JsonValue))
2104
+
2105
+ ConvertToJson = """" & json_DateStr & """"
2106
+ Case VBA.vbString
2107
+ ' String (or large number encoded as string)
2108
+ If Not JsonOptions.UseDoubleForLargeNumbers And json_StringIsLargeNumber(JsonValue) Then
2109
+ ConvertToJson = JsonValue
2110
+ Else
2111
+ ConvertToJson = """" & json_Encode(JsonValue) & """"
2112
+ End If
2113
+ Case VBA.vbBoolean
2114
+ If JsonValue Then
2115
+ ConvertToJson = "true"
2116
+ Else
2117
+ ConvertToJson = "false"
2118
+ End If
2119
+ Case VBA.vbError ' CHANGED: handle vbError
2120
+ Select Case CStr(JsonValue)
2121
+ Case "Error 2007"
2122
+ ConvertToJson = """" & "#DIV/0!" & """"
2123
+ Case "Error 2042"
2124
+ ConvertToJson = """" & "#N/A" & """"
2125
+ Case "Error 2029"
2126
+ ConvertToJson = """" & "#NAME?" & """"
2127
+ Case "Error 2000"
2128
+ ConvertToJson = """" & "#NULL!" & """"
2129
+ Case "Error 2036"
2130
+ ConvertToJson = """" & "#NUM!" & """"
2131
+ Case "Error 2023"
2132
+ ConvertToJson = """" & "#REF!" & """"
2133
+ Case "Error 2015"
2134
+ ConvertToJson = """" & "#VALUE!" & """"
2135
+ End Select
2136
+ Case VBA.vbArray To VBA.vbArray + VBA.vbByte
2137
+ If json_PrettyPrint Then
2138
+ If VBA.VarType(Whitespace) = VBA.vbString Then
2139
+ json_Indentation = VBA.String$(json_CurrentIndentation + 1, Whitespace)
2140
+ json_InnerIndentation = VBA.String$(json_CurrentIndentation + 2, Whitespace)
2141
+ Else
2142
+ json_Indentation = VBA.Space$((json_CurrentIndentation + 1) * Whitespace)
2143
+ json_InnerIndentation = VBA.Space$((json_CurrentIndentation + 2) * Whitespace)
2144
+ End If
2145
+ End If
2146
+
2147
+ ' Array
2148
+ json_BufferAppend json_Buffer, "[", json_BufferPosition, json_BufferLength
2149
+
2150
+ On Error Resume Next
2151
+
2152
+ json_LBound = LBound(JsonValue, 1)
2153
+ json_UBound = UBound(JsonValue, 1)
2154
+ json_LBound2D = LBound(JsonValue, 2)
2155
+ json_UBound2D = UBound(JsonValue, 2)
2156
+
2157
+ If json_LBound >= 0 And json_UBound >= 0 Then
2158
+ For json_Index = json_LBound To json_UBound
2159
+ If json_IsFirstItem Then
2160
+ json_IsFirstItem = False
2161
+ Else
2162
+ ' Append comma to previous line
2163
+ json_BufferAppend json_Buffer, ",", json_BufferPosition, json_BufferLength
2164
+ End If
2165
+
2166
+ If json_LBound2D >= 0 And json_UBound2D >= 0 Then
2167
+ ' 2D Array
2168
+ If json_PrettyPrint Then
2169
+ json_BufferAppend json_Buffer, vbNewLine, json_BufferPosition, json_BufferLength
2170
+ End If
2171
+ json_BufferAppend json_Buffer, json_Indentation & "[", json_BufferPosition, json_BufferLength
2172
+
2173
+ For json_Index2D = json_LBound2D To json_UBound2D
2174
+ If json_IsFirstItem2D Then
2175
+ json_IsFirstItem2D = False
2176
+ Else
2177
+ json_BufferAppend json_Buffer, ",", json_BufferPosition, json_BufferLength
2178
+ End If
2179
+
2180
+ json_Converted = ConvertToJson(JsonValue(json_Index, json_Index2D), Whitespace, json_CurrentIndentation + 2)
2181
+
2182
+ ' For Arrays/Collections, undefined (Empty/Nothing) is treated as null
2183
+ If json_Converted = "" Then
2184
+ ' (nest to only check if converted = "")
2185
+ If json_IsUndefined(JsonValue(json_Index, json_Index2D)) Then
2186
+ json_Converted = "null"
2187
+ End If
2188
+ End If
2189
+
2190
+ If json_PrettyPrint Then
2191
+ json_Converted = vbNewLine & json_InnerIndentation & json_Converted
2192
+ End If
2193
+
2194
+ json_BufferAppend json_Buffer, json_Converted, json_BufferPosition, json_BufferLength
2195
+ Next json_Index2D
2196
+
2197
+ If json_PrettyPrint Then
2198
+ json_BufferAppend json_Buffer, vbNewLine, json_BufferPosition, json_BufferLength
2199
+ End If
2200
+
2201
+ json_BufferAppend json_Buffer, json_Indentation & "]", json_BufferPosition, json_BufferLength
2202
+ json_IsFirstItem2D = True
2203
+ Else
2204
+ ' 1D Array
2205
+ json_Converted = ConvertToJson(JsonValue(json_Index), Whitespace, json_CurrentIndentation + 1)
2206
+
2207
+ ' For Arrays/Collections, undefined (Empty/Nothing) is treated as null
2208
+ If json_Converted = "" Then
2209
+ ' (nest to only check if converted = "")
2210
+ If json_IsUndefined(JsonValue(json_Index)) Then
2211
+ json_Converted = "null"
2212
+ End If
2213
+ End If
2214
+
2215
+ If json_PrettyPrint Then
2216
+ json_Converted = vbNewLine & json_Indentation & json_Converted
2217
+ End If
2218
+
2219
+ json_BufferAppend json_Buffer, json_Converted, json_BufferPosition, json_BufferLength
2220
+ End If
2221
+ Next json_Index
2222
+ End If
2223
+
2224
+ On Error GoTo 0
2225
+
2226
+ If json_PrettyPrint Then
2227
+ json_BufferAppend json_Buffer, vbNewLine, json_BufferPosition, json_BufferLength
2228
+
2229
+ If VBA.VarType(Whitespace) = VBA.vbString Then
2230
+ json_Indentation = VBA.String$(json_CurrentIndentation, Whitespace)
2231
+ Else
2232
+ json_Indentation = VBA.Space$(json_CurrentIndentation * Whitespace)
2233
+ End If
2234
+ End If
2235
+
2236
+ json_BufferAppend json_Buffer, json_Indentation & "]", json_BufferPosition, json_BufferLength
2237
+
2238
+ ConvertToJson = json_BufferToString(json_Buffer, json_BufferPosition)
2239
+
2240
+ ' Dictionary or Collection
2241
+ Case VBA.vbObject
2242
+ If json_PrettyPrint Then
2243
+ If VBA.VarType(Whitespace) = VBA.vbString Then
2244
+ json_Indentation = VBA.String$(json_CurrentIndentation + 1, Whitespace)
2245
+ Else
2246
+ json_Indentation = VBA.Space$((json_CurrentIndentation + 1) * Whitespace)
2247
+ End If
2248
+ End If
2249
+
2250
+ ' Dictionary
2251
+ If VBA.TypeName(JsonValue) = "Dictionary" Then
2252
+ json_BufferAppend json_Buffer, "{", json_BufferPosition, json_BufferLength
2253
+ For Each json_Key In JsonValue.Keys
2254
+ ' For Objects, undefined (Empty/Nothing) is not added to object
2255
+ json_Converted = ConvertToJson(JsonValue(json_Key), Whitespace, json_CurrentIndentation + 1)
2256
+ If json_Converted = "" Then
2257
+ json_SkipItem = json_IsUndefined(JsonValue(json_Key))
2258
+ Else
2259
+ json_SkipItem = False
2260
+ End If
2261
+
2262
+ If Not json_SkipItem Then
2263
+ If json_IsFirstItem Then
2264
+ json_IsFirstItem = False
2265
+ Else
2266
+ json_BufferAppend json_Buffer, ",", json_BufferPosition, json_BufferLength
2267
+ End If
2268
+
2269
+ If json_PrettyPrint Then
2270
+ json_Converted = vbNewLine & json_Indentation & """" & json_Key & """: " & json_Converted
2271
+ Else
2272
+ json_Converted = """" & json_Key & """:" & json_Converted
2273
+ End If
2274
+
2275
+ json_BufferAppend json_Buffer, json_Converted, json_BufferPosition, json_BufferLength
2276
+ End If
2277
+ Next json_Key
2278
+
2279
+ If json_PrettyPrint Then
2280
+ json_BufferAppend json_Buffer, vbNewLine, json_BufferPosition, json_BufferLength
2281
+
2282
+ If VBA.VarType(Whitespace) = VBA.vbString Then
2283
+ json_Indentation = VBA.String$(json_CurrentIndentation, Whitespace)
2284
+ Else
2285
+ json_Indentation = VBA.Space$(json_CurrentIndentation * Whitespace)
2286
+ End If
2287
+ End If
2288
+
2289
+ json_BufferAppend json_Buffer, json_Indentation & "}", json_BufferPosition, json_BufferLength
2290
+
2291
+ ' Collection
2292
+ ElseIf VBA.TypeName(JsonValue) = "Collection" Then
2293
+ json_BufferAppend json_Buffer, "[", json_BufferPosition, json_BufferLength
2294
+ For Each json_Value In JsonValue
2295
+ If json_IsFirstItem Then
2296
+ json_IsFirstItem = False
2297
+ Else
2298
+ json_BufferAppend json_Buffer, ",", json_BufferPosition, json_BufferLength
2299
+ End If
2300
+
2301
+ json_Converted = ConvertToJson(json_Value, Whitespace, json_CurrentIndentation + 1)
2302
+
2303
+ ' For Arrays/Collections, undefined (Empty/Nothing) is treated as null
2304
+ If json_Converted = "" Then
2305
+ ' (nest to only check if converted = "")
2306
+ If json_IsUndefined(json_Value) Then
2307
+ json_Converted = "null"
2308
+ End If
2309
+ End If
2310
+
2311
+ If json_PrettyPrint Then
2312
+ json_Converted = vbNewLine & json_Indentation & json_Converted
2313
+ End If
2314
+
2315
+ json_BufferAppend json_Buffer, json_Converted, json_BufferPosition, json_BufferLength
2316
+ Next json_Value
2317
+
2318
+ If json_PrettyPrint Then
2319
+ json_BufferAppend json_Buffer, vbNewLine, json_BufferPosition, json_BufferLength
2320
+
2321
+ If VBA.VarType(Whitespace) = VBA.vbString Then
2322
+ json_Indentation = VBA.String$(json_CurrentIndentation, Whitespace)
2323
+ Else
2324
+ json_Indentation = VBA.Space$(json_CurrentIndentation * Whitespace)
2325
+ End If
2326
+ End If
2327
+
2328
+ json_BufferAppend json_Buffer, json_Indentation & "]", json_BufferPosition, json_BufferLength
2329
+ End If
2330
+
2331
+ ConvertToJson = json_BufferToString(json_Buffer, json_BufferPosition)
2332
+ Case VBA.vbInteger, VBA.vbLong, VBA.vbSingle, VBA.vbDouble, VBA.vbCurrency, VBA.vbDecimal
2333
+ ' Number (use decimals for numbers)
2334
+ ConvertToJson = VBA.Replace(JsonValue, ",", ".")
2335
+ Case Else
2336
+ ' vbEmpty, vbError, vbDataObject, vbByte, vbUserDefinedType
2337
+ ' Use VBA's built-in to-string
2338
+ On Error Resume Next
2339
+ ConvertToJson = JsonValue
2340
+ On Error GoTo 0
2341
+ End Select
2342
+ End Function
2343
+
2344
+ ' ============================================= '
2345
+ ' Private Functions
2346
+ ' ============================================= '
2347
+
2348
+ Private Function json_ParseObject(json_String As String, ByRef json_Index As Long) As Dictionary
2349
+ Dim json_Key As String
2350
+ Dim json_NextChar As String
2351
+
2352
+ Set json_ParseObject = New Dictionary
2353
+ json_SkipSpaces json_String, json_Index
2354
+ If VBA.Mid$(json_String, json_Index, 1) <> "{" Then
2355
+ Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(json_String, json_Index, "Expecting '{'")
2356
+ Else
2357
+ json_Index = json_Index + 1
2358
+
2359
+ Do
2360
+ json_SkipSpaces json_String, json_Index
2361
+ If VBA.Mid$(json_String, json_Index, 1) = "}" Then
2362
+ json_Index = json_Index + 1
2363
+ Exit Function
2364
+ ElseIf VBA.Mid$(json_String, json_Index, 1) = "," Then
2365
+ json_Index = json_Index + 1
2366
+ json_SkipSpaces json_String, json_Index
2367
+ End If
2368
+
2369
+ json_Key = json_ParseKey(json_String, json_Index)
2370
+ json_NextChar = json_Peek(json_String, json_Index)
2371
+ If json_NextChar = "[" Or json_NextChar = "{" Then
2372
+ Set json_ParseObject.Item(json_Key) = json_ParseValue(json_String, json_Index)
2373
+ Else
2374
+ json_ParseObject.Item(json_Key) = json_ParseValue(json_String, json_Index)
2375
+ End If
2376
+ Loop
2377
+ End If
2378
+ End Function
2379
+
2380
+ Private Function json_ParseArray(json_String As String, ByRef json_Index As Long) As Collection
2381
+ Set json_ParseArray = New Collection
2382
+
2383
+ json_SkipSpaces json_String, json_Index
2384
+ If VBA.Mid$(json_String, json_Index, 1) <> "[" Then
2385
+ Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(json_String, json_Index, "Expecting '['")
2386
+ Else
2387
+ json_Index = json_Index + 1
2388
+
2389
+ Do
2390
+ json_SkipSpaces json_String, json_Index
2391
+ If VBA.Mid$(json_String, json_Index, 1) = "]" Then
2392
+ json_Index = json_Index + 1
2393
+ Exit Function
2394
+ ElseIf VBA.Mid$(json_String, json_Index, 1) = "," Then
2395
+ json_Index = json_Index + 1
2396
+ json_SkipSpaces json_String, json_Index
2397
+ End If
2398
+
2399
+ json_ParseArray.Add json_ParseValue(json_String, json_Index)
2400
+ Loop
2401
+ End If
2402
+ End Function
2403
+
2404
+ Private Function json_ParseValue(json_String As String, ByRef json_Index As Long) As Variant
2405
+ json_SkipSpaces json_String, json_Index
2406
+ Select Case VBA.Mid$(json_String, json_Index, 1)
2407
+ Case "{"
2408
+ Set json_ParseValue = json_ParseObject(json_String, json_Index)
2409
+ Case "["
2410
+ Set json_ParseValue = json_ParseArray(json_String, json_Index)
2411
+ Case """", "'"
2412
+ json_ParseValue = json_ParseString(json_String, json_Index)
2413
+ Case Else
2414
+ If VBA.Mid$(json_String, json_Index, 4) = "true" Then
2415
+ json_ParseValue = True
2416
+ json_Index = json_Index + 4
2417
+ ElseIf VBA.Mid$(json_String, json_Index, 5) = "false" Then
2418
+ json_ParseValue = False
2419
+ json_Index = json_Index + 5
2420
+ ElseIf VBA.Mid$(json_String, json_Index, 4) = "null" Then
2421
+ json_ParseValue = Null
2422
+ json_Index = json_Index + 4
2423
+ ElseIf VBA.InStr("+-0123456789", VBA.Mid$(json_String, json_Index, 1)) Then
2424
+ json_ParseValue = json_ParseNumber(json_String, json_Index)
2425
+ Else
2426
+ Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(json_String, json_Index, "Expecting 'STRING', 'NUMBER', null, true, false, '{', or '['")
2427
+ End If
2428
+ End Select
2429
+ End Function
2430
+
2431
+ Private Function json_ParseString(json_String As String, ByRef json_Index As Long) As String
2432
+ Dim json_Quote As String
2433
+ Dim json_Char As String
2434
+ Dim json_Code As String
2435
+ Dim json_Buffer As String
2436
+ Dim json_BufferPosition As Long
2437
+ Dim json_BufferLength As Long
2438
+
2439
+ json_SkipSpaces json_String, json_Index
2440
+
2441
+ ' Store opening quote to look for matching closing quote
2442
+ json_Quote = VBA.Mid$(json_String, json_Index, 1)
2443
+ json_Index = json_Index + 1
2444
+
2445
+ Do While json_Index > 0 And json_Index <= Len(json_String)
2446
+ json_Char = VBA.Mid$(json_String, json_Index, 1)
2447
+
2448
+ Select Case json_Char
2449
+ Case "\"
2450
+ ' Escaped string, \\, or \/
2451
+ json_Index = json_Index + 1
2452
+ json_Char = VBA.Mid$(json_String, json_Index, 1)
2453
+
2454
+ Select Case json_Char
2455
+ Case """", "\", "/", "'"
2456
+ json_BufferAppend json_Buffer, json_Char, json_BufferPosition, json_BufferLength
2457
+ json_Index = json_Index + 1
2458
+ Case "b"
2459
+ json_BufferAppend json_Buffer, vbBack, json_BufferPosition, json_BufferLength
2460
+ json_Index = json_Index + 1
2461
+ Case "f"
2462
+ json_BufferAppend json_Buffer, vbFormFeed, json_BufferPosition, json_BufferLength
2463
+ json_Index = json_Index + 1
2464
+ Case "n"
2465
+ json_BufferAppend json_Buffer, vbCrLf, json_BufferPosition, json_BufferLength
2466
+ json_Index = json_Index + 1
2467
+ Case "r"
2468
+ json_BufferAppend json_Buffer, vbCr, json_BufferPosition, json_BufferLength
2469
+ json_Index = json_Index + 1
2470
+ Case "t"
2471
+ json_BufferAppend json_Buffer, vbTab, json_BufferPosition, json_BufferLength
2472
+ json_Index = json_Index + 1
2473
+ Case "u"
2474
+ ' Unicode character escape (e.g. \u00a9 = Copyright)
2475
+ json_Index = json_Index + 1
2476
+ json_Code = VBA.Mid$(json_String, json_Index, 4)
2477
+ json_BufferAppend json_Buffer, VBA.ChrW(VBA.Val("&h" + json_Code)), json_BufferPosition, json_BufferLength
2478
+ json_Index = json_Index + 4
2479
+ End Select
2480
+ Case json_Quote
2481
+ json_ParseString = json_BufferToString(json_Buffer, json_BufferPosition)
2482
+ json_Index = json_Index + 1
2483
+ Exit Function
2484
+ Case Else
2485
+ json_BufferAppend json_Buffer, json_Char, json_BufferPosition, json_BufferLength
2486
+ json_Index = json_Index + 1
2487
+ End Select
2488
+ Loop
2489
+ End Function
2490
+
2491
+ Private Function json_ParseNumber(json_String As String, ByRef json_Index As Long) As Variant
2492
+ Dim json_Char As String
2493
+ Dim json_Value As String
2494
+ Dim json_IsLargeNumber As Boolean
2495
+
2496
+ json_SkipSpaces json_String, json_Index
2497
+
2498
+ Do While json_Index > 0 And json_Index <= Len(json_String)
2499
+ json_Char = VBA.Mid$(json_String, json_Index, 1)
2500
+
2501
+ If VBA.InStr("+-0123456789.eE", json_Char) Then
2502
+ ' Unlikely to have massive number, so use simple append rather than buffer here
2503
+ json_Value = json_Value & json_Char
2504
+ json_Index = json_Index + 1
2505
+ Else
2506
+ ' Excel only stores 15 significant digits, so any numbers larger than that are truncated
2507
+ ' This can lead to issues when BIGINT's are used (e.g. for Ids or Credit Cards), as they will be invalid above 15 digits
2508
+ ' See: http://support.microsoft.com/kb/269370
2509
+ '
2510
+ ' Fix: Parse -> String, Convert -> String longer than 15/16 characters containing only numbers and decimal points -> Number
2511
+ ' (decimal doesn't factor into significant digit count, so if present check for 15 digits + decimal = 16)
2512
+ json_IsLargeNumber = IIf(InStr(json_Value, "."), Len(json_Value) >= 17, Len(json_Value) >= 16)
2513
+ If Not JsonOptions.UseDoubleForLargeNumbers And json_IsLargeNumber Then
2514
+ json_ParseNumber = json_Value
2515
+ Else
2516
+ ' VBA.Val does not use regional settings, so guard for comma is not needed
2517
+ json_ParseNumber = VBA.Val(json_Value)
2518
+ End If
2519
+ Exit Function
2520
+ End If
2521
+ Loop
2522
+ End Function
2523
+
2524
+ Private Function json_ParseKey(json_String As String, ByRef json_Index As Long) As String
2525
+ ' Parse key with single or double quotes
2526
+ If VBA.Mid$(json_String, json_Index, 1) = """" Or VBA.Mid$(json_String, json_Index, 1) = "'" Then
2527
+ json_ParseKey = json_ParseString(json_String, json_Index)
2528
+ ElseIf JsonOptions.AllowUnquotedKeys Then
2529
+ Dim json_Char As String
2530
+ Do While json_Index > 0 And json_Index <= Len(json_String)
2531
+ json_Char = VBA.Mid$(json_String, json_Index, 1)
2532
+ If (json_Char <> " ") And (json_Char <> ":") Then
2533
+ json_ParseKey = json_ParseKey & json_Char
2534
+ json_Index = json_Index + 1
2535
+ Else
2536
+ Exit Do
2537
+ End If
2538
+ Loop
2539
+ Else
2540
+ Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(json_String, json_Index, "Expecting '""' or '''")
2541
+ End If
2542
+
2543
+ ' Check for colon and skip if present or throw if not present
2544
+ json_SkipSpaces json_String, json_Index
2545
+ If VBA.Mid$(json_String, json_Index, 1) <> ":" Then
2546
+ Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(json_String, json_Index, "Expecting ':'")
2547
+ Else
2548
+ json_Index = json_Index + 1
2549
+ End If
2550
+ End Function
2551
+
2552
+ Private Function json_IsUndefined(ByVal json_Value As Variant) As Boolean
2553
+ ' Empty / Nothing -> undefined
2554
+ Select Case VBA.VarType(json_Value)
2555
+ Case VBA.vbEmpty
2556
+ json_IsUndefined = True
2557
+ Case VBA.vbObject
2558
+ Select Case VBA.TypeName(json_Value)
2559
+ Case "Empty", "Nothing"
2560
+ json_IsUndefined = True
2561
+ End Select
2562
+ End Select
2563
+ End Function
2564
+
2565
+ Private Function json_Encode(ByVal json_Text As Variant) As String
2566
+ ' Reference: http://www.ietf.org/rfc/rfc4627.txt
2567
+ ' Escape: ", \, /, backspace, form feed, line feed, carriage return, tab
2568
+ Dim json_Index As Long
2569
+ Dim json_Char As String
2570
+ Dim json_AscCode As Long
2571
+ Dim json_Buffer As String
2572
+ Dim json_BufferPosition As Long
2573
+ Dim json_BufferLength As Long
2574
+
2575
+ For json_Index = 1 To VBA.Len(json_Text)
2576
+ json_Char = VBA.Mid$(json_Text, json_Index, 1)
2577
+ json_AscCode = VBA.AscW(json_Char)
2578
+
2579
+ ' When AscW returns a negative number, it returns the twos complement form of that number.
2580
+ ' To convert the twos complement notation into normal binary notation, add 0xFFF to the return result.
2581
+ ' https://support.microsoft.com/en-us/kb/272138
2582
+ If json_AscCode < 0 Then
2583
+ json_AscCode = json_AscCode + 65536
2584
+ End If
2585
+
2586
+ ' From spec, ", \, and control characters must be escaped (solidus is optional)
2587
+
2588
+ Select Case json_AscCode
2589
+ Case 34
2590
+ ' " -> 34 -> \"
2591
+ json_Char = "\"""
2592
+ Case 92
2593
+ ' \ -> 92 -> \\
2594
+ json_Char = "\\"
2595
+ Case 47
2596
+ ' / -> 47 -> \/ (optional)
2597
+ If JsonOptions.EscapeSolidus Then
2598
+ json_Char = "\/"
2599
+ End If
2600
+ Case 8
2601
+ ' backspace -> 8 -> \b
2602
+ json_Char = "\b"
2603
+ Case 12
2604
+ ' form feed -> 12 -> \f
2605
+ json_Char = "\f"
2606
+ Case 10
2607
+ ' line feed -> 10 -> \n
2608
+ json_Char = "\n"
2609
+ Case 13
2610
+ ' carriage return -> 13 -> \r
2611
+ json_Char = "\r"
2612
+ Case 9
2613
+ ' tab -> 9 -> \t
2614
+ json_Char = "\t"
2615
+ Case 0 To 31, 127 To 65535
2616
+ ' Non-ascii characters -> convert to 4-digit hex
2617
+ json_Char = "\u" & VBA.Right$("0000" & VBA.Hex$(json_AscCode), 4)
2618
+ End Select
2619
+
2620
+ json_BufferAppend json_Buffer, json_Char, json_BufferPosition, json_BufferLength
2621
+ Next json_Index
2622
+
2623
+ json_Encode = json_BufferToString(json_Buffer, json_BufferPosition)
2624
+ End Function
2625
+
2626
+ Private Function json_Peek(json_String As String, ByVal json_Index As Long, Optional json_NumberOfCharacters As Long = 1) As String
2627
+ ' "Peek" at the next number of characters without incrementing json_Index (ByVal instead of ByRef)
2628
+ json_SkipSpaces json_String, json_Index
2629
+ json_Peek = VBA.Mid$(json_String, json_Index, json_NumberOfCharacters)
2630
+ End Function
2631
+
2632
+ Private Sub json_SkipSpaces(json_String As String, ByRef json_Index As Long)
2633
+ ' Increment index to skip over spaces
2634
+ Do While json_Index > 0 And json_Index <= VBA.Len(json_String) And VBA.Mid$(json_String, json_Index, 1) = " "
2635
+ json_Index = json_Index + 1
2636
+ Loop
2637
+ End Sub
2638
+
2639
+ Private Function json_StringIsLargeNumber(json_String As Variant) As Boolean
2640
+ ' Check if the given string is considered a "large number"
2641
+ ' (See json_ParseNumber)
2642
+
2643
+ Dim json_Length As Long
2644
+ Dim json_CharIndex As Long
2645
+ json_Length = VBA.Len(json_String)
2646
+
2647
+ ' Length with be at least 16 characters and assume will be less than 100 characters
2648
+ If json_Length >= 16 And json_Length <= 100 Then
2649
+ Dim json_CharCode As String
2650
+
2651
+ json_StringIsLargeNumber = True
2652
+
2653
+ For json_CharIndex = 1 To json_Length
2654
+ json_CharCode = VBA.Asc(VBA.Mid$(json_String, json_CharIndex, 1))
2655
+ Select Case json_CharCode
2656
+ ' Look for .|0-9|E|e
2657
+ Case 46, 48 To 57, 69, 101
2658
+ ' Continue through characters
2659
+ Case Else
2660
+ json_StringIsLargeNumber = False
2661
+ Exit Function
2662
+ End Select
2663
+ Next json_CharIndex
2664
+ End If
2665
+ End Function
2666
+
2667
+ Private Function json_ParseErrorMessage(json_String As String, ByRef json_Index As Long, ErrorMessage As String)
2668
+ ' Provide detailed parse error message, including details of where and what occurred
2669
+ '
2670
+ ' Example:
2671
+ ' Error parsing JSON:
2672
+ ' {"abcde":True}
2673
+ ' ^
2674
+ ' Expecting 'STRING', 'NUMBER', null, true, false, '{', or '['
2675
+
2676
+ Dim json_StartIndex As Long
2677
+ Dim json_StopIndex As Long
2678
+
2679
+ ' Include 10 characters before and after error (if possible)
2680
+ json_StartIndex = json_Index - 10
2681
+ json_StopIndex = json_Index + 10
2682
+ If json_StartIndex <= 0 Then
2683
+ json_StartIndex = 1
2684
+ End If
2685
+ If json_StopIndex > VBA.Len(json_String) Then
2686
+ json_StopIndex = VBA.Len(json_String)
2687
+ End If
2688
+
2689
+ json_ParseErrorMessage = "Error parsing JSON:" & VBA.vbNewLine & _
2690
+ VBA.Mid$(json_String, json_StartIndex, json_StopIndex - json_StartIndex + 1) & VBA.vbNewLine & _
2691
+ VBA.Space$(json_Index - json_StartIndex) & "^" & VBA.vbNewLine & _
2692
+ ErrorMessage
2693
+ End Function
2694
+
2695
+ Private Sub json_BufferAppend(ByRef json_Buffer As String, _
2696
+ ByRef json_Append As Variant, _
2697
+ ByRef json_BufferPosition As Long, _
2698
+ ByRef json_BufferLength As Long)
2699
+ ' VBA can be slow to append strings due to allocating a new string for each append
2700
+ ' Instead of using the traditional append, allocate a large empty string and then copy string at append position
2701
+ '
2702
+ ' Example:
2703
+ ' Buffer: "abc "
2704
+ ' Append: "def"
2705
+ ' Buffer Position: 3
2706
+ ' Buffer Length: 5
2707
+ '
2708
+ ' Buffer position + Append length > Buffer length -> Append chunk of blank space to buffer
2709
+ ' Buffer: "abc "
2710
+ ' Buffer Length: 10
2711
+ '
2712
+ ' Put "def" into buffer at position 3 (0-based)
2713
+ ' Buffer: "abcdef "
2714
+ '
2715
+ ' Approach based on cStringBuilder from vbAccelerator
2716
+ ' http://www.vbaccelerator.com/home/VB/Code/Techniques/RunTime_Debug_Tracing/VB6_Tracer_Utility_zip_cStringBuilder_cls.asp
2717
+ '
2718
+ ' and clsStringAppend from Philip Swannell
2719
+ ' https://github.com/VBA-tools/VBA-JSON/pull/82
2720
+
2721
+ Dim json_AppendLength As Long
2722
+ Dim json_LengthPlusPosition As Long
2723
+
2724
+ json_AppendLength = VBA.Len(json_Append)
2725
+ json_LengthPlusPosition = json_AppendLength + json_BufferPosition
2726
+
2727
+ If json_LengthPlusPosition > json_BufferLength Then
2728
+ ' Appending would overflow buffer, add chunk
2729
+ ' (double buffer length or append length, whichever is bigger)
2730
+ Dim json_AddedLength As Long
2731
+ json_AddedLength = IIf(json_AppendLength > json_BufferLength, json_AppendLength, json_BufferLength)
2732
+
2733
+ json_Buffer = json_Buffer & VBA.Space$(json_AddedLength)
2734
+ json_BufferLength = json_BufferLength + json_AddedLength
2735
+ End If
2736
+
2737
+ ' Note: Namespacing with VBA.Mid$ doesn't work properly here, throwing compile error:
2738
+ ' Function call on left-hand side of assignment must return Variant or Object
2739
+ Mid$(json_Buffer, json_BufferPosition + 1, json_AppendLength) = CStr(json_Append)
2740
+ json_BufferPosition = json_BufferPosition + json_AppendLength
2741
+ End Sub
2742
+
2743
+ Private Function json_BufferToString(ByRef json_Buffer As String, ByVal json_BufferPosition As Long) As String
2744
+ If json_BufferPosition > 0 Then
2745
+ json_BufferToString = VBA.Left$(json_Buffer, json_BufferPosition)
2746
+ End If
2747
+ End Function
2748
+
2749
+ ''
2750
+ ' VBA-UTC v1.0.6
2751
+ ' (c) Tim Hall - https://github.com/VBA-tools/VBA-UtcConverter
2752
+ '
2753
+ ' UTC/ISO 8601 Converter for VBA
2754
+ '
2755
+ ' Errors:
2756
+ ' 10011 - UTC parsing error
2757
+ ' 10012 - UTC conversion error
2758
+ ' 10013 - ISO 8601 parsing error
2759
+ ' 10014 - ISO 8601 conversion error
2760
+ '
2761
+ ' @module UtcConverter
2762
+ ' @author tim.hall.engr@gmail.com
2763
+ ' @license MIT (http://www.opensource.org/licenses/mit-license.php)
2764
+ '' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ '
2765
+
2766
+ ' (Declarations moved to top)
2767
+
2768
+ ' ============================================= '
2769
+ ' Public Methods
2770
+ ' ============================================= '
2771
+
2772
+ ''
2773
+ ' Parse UTC date to local date
2774
+ '
2775
+ ' @method ParseUtc
2776
+ ' @param {Date} UtcDate
2777
+ ' @return {Date} Local date
2778
+ ' @throws 10011 - UTC parsing error
2779
+ ''
2780
+ Public Function ParseUtc(utc_UtcDate As Date) As Date
2781
+ On Error GoTo utc_ErrorHandling
2782
+
2783
+ #If Mac Then
2784
+ ParseUtc = utc_ConvertDate(utc_UtcDate)
2785
+ #Else
2786
+ Dim utc_TimeZoneInfo As utc_TIME_ZONE_INFORMATION
2787
+ Dim utc_LocalDate As utc_SYSTEMTIME
2788
+
2789
+ utc_GetTimeZoneInformation utc_TimeZoneInfo
2790
+ utc_SystemTimeToTzSpecificLocalTime utc_TimeZoneInfo, utc_DateToSystemTime(utc_UtcDate), utc_LocalDate
2791
+
2792
+ ParseUtc = utc_SystemTimeToDate(utc_LocalDate)
2793
+ #End If
2794
+
2795
+ Exit Function
2796
+
2797
+ utc_ErrorHandling:
2798
+ Err.Raise 10011, "UtcConverter.ParseUtc", "UTC parsing error: " & Err.Number & " - " & Err.Description
2799
+ End Function
2800
+
2801
+ ''
2802
+ ' Convert local date to UTC date
2803
+ '
2804
+ ' @method ConvertToUrc
2805
+ ' @param {Date} utc_LocalDate
2806
+ ' @return {Date} UTC date
2807
+ ' @throws 10012 - UTC conversion error
2808
+ ''
2809
+ Public Function ConvertToUtc(utc_LocalDate As Date) As Date
2810
+ On Error GoTo utc_ErrorHandling
2811
+
2812
+ #If Mac Then
2813
+ ConvertToUtc = utc_ConvertDate(utc_LocalDate, utc_ConvertToUtc:=True)
2814
+ #Else
2815
+ Dim utc_TimeZoneInfo As utc_TIME_ZONE_INFORMATION
2816
+ Dim utc_UtcDate As utc_SYSTEMTIME
2817
+
2818
+ utc_GetTimeZoneInformation utc_TimeZoneInfo
2819
+ utc_TzSpecificLocalTimeToSystemTime utc_TimeZoneInfo, utc_DateToSystemTime(utc_LocalDate), utc_UtcDate
2820
+
2821
+ ConvertToUtc = utc_SystemTimeToDate(utc_UtcDate)
2822
+ #End If
2823
+
2824
+ Exit Function
2825
+
2826
+ utc_ErrorHandling:
2827
+ Err.Raise 10012, "UtcConverter.ConvertToUtc", "UTC conversion error: " & Err.Number & " - " & Err.Description
2828
+ End Function
2829
+
2830
+ ''
2831
+ ' CHANGED: Date/Time cells are timezone naive, so don't do any conversion!
2832
+ ' Parse ISO 8601 date string to local date
2833
+ '
2834
+ ' @method ParseIso
2835
+ ' @param {Date} utc_IsoString
2836
+ ' @return {Date} Local date
2837
+ ' @throws 10013 - ISO 8601 parsing error
2838
+ ''
2839
+ Public Function ParseIso(utc_IsoString As String) As Date
2840
+ On Error GoTo utc_ErrorHandling
2841
+
2842
+ Dim utc_Parts() As String
2843
+ Dim utc_DateParts() As String
2844
+ Dim utc_TimeParts() As String
2845
+ Dim utc_OffsetIndex As Long
2846
+ Dim utc_HasOffset As Boolean
2847
+ Dim utc_NegativeOffset As Boolean
2848
+ Dim utc_OffsetParts() As String
2849
+ Dim utc_Offset As Date
2850
+
2851
+ utc_Parts = VBA.Split(utc_IsoString, "T")
2852
+ utc_DateParts = VBA.Split(utc_Parts(0), "-")
2853
+ ParseIso = VBA.DateSerial(VBA.CInt(utc_DateParts(0)), VBA.CInt(utc_DateParts(1)), VBA.CInt(utc_DateParts(2)))
2854
+
2855
+ If UBound(utc_Parts) > 0 Then
2856
+ If VBA.InStr(utc_Parts(1), "Z") Then
2857
+ utc_TimeParts = VBA.Split(VBA.Replace(utc_Parts(1), "Z", ""), ":")
2858
+ Else
2859
+ utc_OffsetIndex = VBA.InStr(1, utc_Parts(1), "+")
2860
+ If utc_OffsetIndex = 0 Then
2861
+ utc_NegativeOffset = True
2862
+ utc_OffsetIndex = VBA.InStr(1, utc_Parts(1), "-")
2863
+ End If
2864
+
2865
+ If utc_OffsetIndex > 0 Then
2866
+ utc_HasOffset = True
2867
+ utc_TimeParts = VBA.Split(VBA.Left$(utc_Parts(1), utc_OffsetIndex - 1), ":")
2868
+ utc_OffsetParts = VBA.Split(VBA.Right$(utc_Parts(1), Len(utc_Parts(1)) - utc_OffsetIndex), ":")
2869
+
2870
+ Select Case UBound(utc_OffsetParts)
2871
+ Case 0
2872
+ utc_Offset = TimeSerial(VBA.CInt(utc_OffsetParts(0)), 0, 0)
2873
+ Case 1
2874
+ utc_Offset = TimeSerial(VBA.CInt(utc_OffsetParts(0)), VBA.CInt(utc_OffsetParts(1)), 0)
2875
+ Case 2
2876
+ ' VBA.Val does not use regional settings, use for seconds to avoid decimal/comma issues
2877
+ utc_Offset = TimeSerial(VBA.CInt(utc_OffsetParts(0)), VBA.CInt(utc_OffsetParts(1)), Int(VBA.Val(utc_OffsetParts(2))))
2878
+ End Select
2879
+
2880
+ If utc_NegativeOffset Then: utc_Offset = -utc_Offset
2881
+ Else
2882
+ utc_TimeParts = VBA.Split(utc_Parts(1), ":")
2883
+ End If
2884
+ End If
2885
+
2886
+ Select Case UBound(utc_TimeParts)
2887
+ Case 0
2888
+ ParseIso = ParseIso + VBA.TimeSerial(VBA.CInt(utc_TimeParts(0)), 0, 0)
2889
+ Case 1
2890
+ ParseIso = ParseIso + VBA.TimeSerial(VBA.CInt(utc_TimeParts(0)), VBA.CInt(utc_TimeParts(1)), 0)
2891
+ Case 2
2892
+ ' VBA.Val does not use regional settings, use for seconds to avoid decimal/comma issues
2893
+ ParseIso = ParseIso + VBA.TimeSerial(VBA.CInt(utc_TimeParts(0)), VBA.CInt(utc_TimeParts(1)), Int(VBA.Val(utc_TimeParts(2))))
2894
+ End Select
2895
+
2896
+ 'CHANGED: Don't do any timezone conversion
2897
+ 'ParseIso = ParseUtc(ParseIso)
2898
+
2899
+ 'If utc_HasOffset Then
2900
+ ' ParseIso = ParseIso - utc_Offset
2901
+ 'End If
2902
+ End If
2903
+
2904
+ Exit Function
2905
+
2906
+ utc_ErrorHandling:
2907
+ Err.Raise 10013, "UtcConverter.ParseIso", "ISO 8601 parsing error for " & utc_IsoString & ": " & Err.Number & " - " & Err.Description
2908
+ End Function
2909
+
2910
+ ''
2911
+ ' CHANGED: Date/Time cells are timezone naive, so don't do any conversion!
2912
+ ' Convert local date to ISO 8601 string
2913
+ '
2914
+ ' @method ConvertToIso
2915
+ ' @param {Date} utc_LocalDate
2916
+ ' @return {Date} ISO 8601 string
2917
+ ' @throws 10014 - ISO 8601 conversion error
2918
+ ''
2919
+ Public Function ConvertToIso(utc_LocalDate As Date) As String
2920
+ On Error GoTo utc_ErrorHandling
2921
+
2922
+ ' CHANGED: Removed ConvertToUtc
2923
+ 'ConvertToIso = VBA.Format$(ConvertToUtc(utc_LocalDate), "yyyy-mm-ddTHH:mm:ss.000Z")
2924
+ ConvertToIso = VBA.Format$(utc_LocalDate, "yyyy-mm-ddTHH:mm:ss.000Z")
2925
+
2926
+ Exit Function
2927
+
2928
+ utc_ErrorHandling:
2929
+ Err.Raise 10014, "UtcConverter.ConvertToIso", "ISO 8601 conversion error: " & Err.Number & " - " & Err.Description
2930
+ End Function
2931
+
2932
+ ' ============================================= '
2933
+ ' Private Functions
2934
+ ' ============================================= '
2935
+
2936
+ #If Mac Then
2937
+
2938
+ Private Function utc_ConvertDate(utc_Value As Date, Optional utc_ConvertToUtc As Boolean = False) As Date
2939
+ Dim utc_ShellCommand As String
2940
+ Dim utc_Result As utc_ShellResult
2941
+ Dim utc_Parts() As String
2942
+ Dim utc_DateParts() As String
2943
+ Dim utc_TimeParts() As String
2944
+
2945
+ If utc_ConvertToUtc Then
2946
+ utc_ShellCommand = "date -ur `date -jf '%Y-%m-%d %H:%M:%S' " & _
2947
+ "'" & VBA.Format$(utc_Value, "yyyy-mm-dd HH:mm:ss") & "' " & _
2948
+ " +'%s'` +'%Y-%m-%d %H:%M:%S'"
2949
+ Else
2950
+ utc_ShellCommand = "date -jf '%Y-%m-%d %H:%M:%S %z' " & _
2951
+ "'" & VBA.Format$(utc_Value, "yyyy-mm-dd HH:mm:ss") & " +0000' " & _
2952
+ "+'%Y-%m-%d %H:%M:%S'"
2953
+ End If
2954
+
2955
+ utc_Result = utc_ExecuteInShell(utc_ShellCommand)
2956
+
2957
+ If utc_Result.utc_Output = "" Then
2958
+ Err.Raise 10015, "UtcConverter.utc_ConvertDate", "'date' command failed"
2959
+ Else
2960
+ utc_Parts = Split(utc_Result.utc_Output, " ")
2961
+ utc_DateParts = Split(utc_Parts(0), "-")
2962
+ utc_TimeParts = Split(utc_Parts(1), ":")
2963
+
2964
+ utc_ConvertDate = DateSerial(utc_DateParts(0), utc_DateParts(1), utc_DateParts(2)) + _
2965
+ TimeSerial(utc_TimeParts(0), utc_TimeParts(1), utc_TimeParts(2))
2966
+ End If
2967
+ End Function
2968
+
2969
+ Private Function utc_ExecuteInShell(utc_ShellCommand As String) As utc_ShellResult
2970
+ #If VBA7 Then
2971
+ Dim utc_File As LongPtr
2972
+ Dim utc_Read As LongPtr
2973
+ #Else
2974
+ Dim utc_File As Long
2975
+ Dim utc_Read As Long
2976
+ #End If
2977
+
2978
+ Dim utc_Chunk As String
2979
+
2980
+ On Error GoTo utc_ErrorHandling
2981
+ utc_File = utc_popen(utc_ShellCommand, "r")
2982
+
2983
+ If utc_File = 0 Then: Exit Function
2984
+
2985
+ Do While utc_feof(utc_File) = 0
2986
+ utc_Chunk = VBA.Space$(50)
2987
+ utc_Read = CLng(utc_fread(utc_Chunk, 1, Len(utc_Chunk) - 1, utc_File))
2988
+ If utc_Read > 0 Then
2989
+ utc_Chunk = VBA.Left$(utc_Chunk, CLng(utc_Read))
2990
+ utc_ExecuteInShell.utc_Output = utc_ExecuteInShell.utc_Output & utc_Chunk
2991
+ End If
2992
+ Loop
2993
+
2994
+ utc_ErrorHandling:
2995
+ utc_ExecuteInShell.utc_ExitCode = CLng(utc_pclose(utc_File))
2996
+ End Function
2997
+
2998
+ #Else
2999
+
3000
+ Private Function utc_DateToSystemTime(utc_Value As Date) As utc_SYSTEMTIME
3001
+ utc_DateToSystemTime.utc_wYear = VBA.Year(utc_Value)
3002
+ utc_DateToSystemTime.utc_wMonth = VBA.Month(utc_Value)
3003
+ utc_DateToSystemTime.utc_wDay = VBA.Day(utc_Value)
3004
+ utc_DateToSystemTime.utc_wHour = VBA.Hour(utc_Value)
3005
+ utc_DateToSystemTime.utc_wMinute = VBA.Minute(utc_Value)
3006
+ utc_DateToSystemTime.utc_wSecond = VBA.Second(utc_Value)
3007
+ utc_DateToSystemTime.utc_wMilliseconds = 0
3008
+ End Function
3009
+
3010
+ Private Function utc_SystemTimeToDate(utc_Value As utc_SYSTEMTIME) As Date
3011
+ utc_SystemTimeToDate = DateSerial(utc_Value.utc_wYear, utc_Value.utc_wMonth, utc_Value.utc_wDay) + _
3012
+ TimeSerial(utc_Value.utc_wHour, utc_Value.utc_wMinute, utc_Value.utc_wSecond)
3013
+ End Function
3014
+
3015
+ #End If
3016
+
3017
+ ''
3018
+ ' AutoProxy 1.0.2
3019
+ ' (c) Damien Thirion
3020
+ '
3021
+ ' Auto configure proxy server
3022
+ '
3023
+ ' Based on code shared by Stephen Sulzer
3024
+ ' https://groups.google.com/d/msg/microsoft.public.winhttp/ZeWN2Xig82g/jgHIBDSfBwsJ
3025
+ '
3026
+ ' Errors:
3027
+ ' 11020 - Unknown error while detecting proxy
3028
+ ' 11021 - WPAD detection failed
3029
+ ' 11022 - Unable to download proxy auto-config script
3030
+ ' 11023 - Error in proxy auto-config script
3031
+ ' 11024 - No proxy can be located for the specified URL
3032
+ ' 11025 - Specified URL is not valid
3033
+ '
3034
+ ' @license MIT (http://www.opensource.org/licenses/mit-license.php)
3035
+ '
3036
+ '' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ '
3037
+
3038
+ ''
3039
+ ' Returns IE proxy settings
3040
+ ' including auto-detection and auto-config scripts results
3041
+ '
3042
+ ' @param {String} Url
3043
+ ' @param[out] {String} ProxyServer
3044
+ ' @param[out] {String} ProxyBypass
3045
+ ''
3046
+ Public Sub GetAutoProxy(ByVal url As String, ByRef proxyServer As String, ByRef ProxyBypass As String)
3047
+ #If Mac Then
3048
+ ' (Windows only)
3049
+ #ElseIf VBA7 Then
3050
+ Dim AutoProxy_ProxyStringPtr As LongPtr
3051
+ Dim AutoProxy_ptr As LongPtr
3052
+ Dim AutoProxy_hSession As LongPtr
3053
+ #Else
3054
+ Dim AutoProxy_ProxyStringPtr As Long
3055
+ Dim AutoProxy_ptr As Long
3056
+ Dim AutoProxy_hSession As Long
3057
+ #End If
3058
+ #If Mac Then
3059
+ #Else
3060
+ Dim AutoProxy_IEProxyConfig As AUTOPROXY_IE_PROXY_CONFIG
3061
+ Dim AutoProxy_AutoProxyOptions As AUTOPROXY_OPTIONS
3062
+ Dim AutoProxy_ProxyInfo As AUTOPROXY_INFO
3063
+ Dim AutoProxy_doAutoProxy As Boolean
3064
+ Dim AutoProxy_Error As Long
3065
+ Dim AutoProxy_ErrorMsg As String
3066
+
3067
+ AutoProxy_AutoProxyOptions.AutoProxy_fAutoLogonIfChallenged = 1
3068
+ proxyServer = ""
3069
+ ProxyBypass = ""
3070
+
3071
+ ' WinHttpGetProxyForUrl returns unexpected errors if Url is empty
3072
+ If url = "" Then url = " "
3073
+
3074
+ On Error GoTo AutoProxy_Cleanup
3075
+
3076
+ ' Check IE's proxy configuration
3077
+ If (AutoProxy_GetIEProxy(AutoProxy_IEProxyConfig) > 0) Then
3078
+ ' If IE is configured to auto-detect, then we will too.
3079
+ If (AutoProxy_IEProxyConfig.AutoProxy_fAutoDetect <> 0) Then
3080
+ AutoProxy_AutoProxyOptions.AutoProxy_dwFlags = AUTOPROXY_AUTO_DETECT
3081
+ AutoProxy_AutoProxyOptions.AutoProxy_dwAutoDetectFlags = _
3082
+ AUTOPROXY_DETECT_TYPE_DHCP + AUTOPROXY_DETECT_TYPE_DNS
3083
+ AutoProxy_doAutoProxy = True
3084
+ End If
3085
+
3086
+ ' If IE is configured to use an auto-config script, then
3087
+ ' we will use it too
3088
+ If (AutoProxy_IEProxyConfig.AutoProxy_lpszAutoConfigUrl <> 0) Then
3089
+ AutoProxy_AutoProxyOptions.AutoProxy_dwFlags = _
3090
+ AutoProxy_AutoProxyOptions.AutoProxy_dwFlags + AUTOPROXY_CONFIG_URL
3091
+ AutoProxy_AutoProxyOptions.AutoProxy_lpszAutoConfigUrl = AutoProxy_IEProxyConfig.AutoProxy_lpszAutoConfigUrl
3092
+ AutoProxy_doAutoProxy = True
3093
+ End If
3094
+ Else
3095
+ ' If the IE proxy config is not available, then
3096
+ ' we will try auto-detection
3097
+ AutoProxy_AutoProxyOptions.AutoProxy_dwFlags = AUTOPROXY_AUTO_DETECT
3098
+ AutoProxy_AutoProxyOptions.AutoProxy_dwAutoDetectFlags = _
3099
+ AUTOPROXY_DETECT_TYPE_DHCP + AUTOPROXY_DETECT_TYPE_DNS
3100
+ AutoProxy_doAutoProxy = True
3101
+ End If
3102
+
3103
+ If AutoProxy_doAutoProxy Then
3104
+ On Error GoTo AutoProxy_TryIEFallback
3105
+
3106
+ ' Need to create a temporary WinHttp session handle
3107
+ ' Note: Performance of this GetProxyInfoForUrl function can be
3108
+ ' improved by saving this AutoProxy_hSession handle across calls
3109
+ ' instead of creating a new handle each time
3110
+ AutoProxy_hSession = AutoProxy_HttpOpen(0, 1, 0, 0, 0)
3111
+
3112
+ If (AutoProxy_GetProxyForUrl( _
3113
+ AutoProxy_hSession, StrPtr(url), AutoProxy_AutoProxyOptions, AutoProxy_ProxyInfo) > 0) Then
3114
+
3115
+ AutoProxy_ProxyStringPtr = AutoProxy_ProxyInfo.AutoProxy_lpszProxy
3116
+ Else
3117
+ AutoProxy_Error = Err.LastDllError
3118
+ Select Case AutoProxy_Error
3119
+ Case 12180
3120
+ AutoProxy_ErrorMsg = "WPAD detection failed"
3121
+ AutoProxy_Error = 10021
3122
+ Case 12167
3123
+ AutoProxy_ErrorMsg = "Unable to download proxy auto-config script"
3124
+ AutoProxy_Error = 10022
3125
+ Case 12166
3126
+ AutoProxy_ErrorMsg = "Error in proxy auto-config script"
3127
+ AutoProxy_Error = 10023
3128
+ Case 12178
3129
+ AutoProxy_ErrorMsg = "No proxy can be located for the specified URL"
3130
+ AutoProxy_Error = 10024
3131
+ Case 12005, 12006
3132
+ AutoProxy_ErrorMsg = "Specified URL is not valid"
3133
+ AutoProxy_Error = 10025
3134
+ Case Else
3135
+ AutoProxy_ErrorMsg = "Unknown error while detecting proxy"
3136
+ AutoProxy_Error = 10020
3137
+ End Select
3138
+ End If
3139
+
3140
+ AutoProxy_HttpClose AutoProxy_hSession
3141
+ AutoProxy_hSession = 0
3142
+ End If
3143
+
3144
+ AutoProxy_TryIEFallback:
3145
+ On Error GoTo AutoProxy_Cleanup
3146
+
3147
+ ' If we don't have a proxy server from WinHttpGetProxyForUrl,
3148
+ ' then pick one up from the IE proxy config (if given)
3149
+ If (AutoProxy_ProxyStringPtr = 0) Then
3150
+ AutoProxy_ProxyStringPtr = AutoProxy_IEProxyConfig.AutoProxy_lpszProxy
3151
+ End If
3152
+
3153
+ ' If there's a proxy string, convert it to a Basic string
3154
+ If (AutoProxy_ProxyStringPtr <> 0) Then
3155
+ AutoProxy_ptr = AutoProxy_SysAllocString(AutoProxy_ProxyStringPtr)
3156
+ AutoProxy_CopyMemory VarPtr(proxyServer), VarPtr(AutoProxy_ptr), 4
3157
+ End If
3158
+
3159
+ ' Pick up any bypass string from the IEProxyConfig
3160
+ If (AutoProxy_IEProxyConfig.AutoProxy_lpszProxyBypass <> 0) Then
3161
+ AutoProxy_ptr = AutoProxy_SysAllocString(AutoProxy_IEProxyConfig.AutoProxy_lpszProxyBypass)
3162
+ AutoProxy_CopyMemory VarPtr(ProxyBypass), VarPtr(AutoProxy_ptr), 4
3163
+ End If
3164
+
3165
+ ' Ensure WinHttp session is closed, an error might have occurred
3166
+ If (AutoProxy_hSession <> 0) Then
3167
+ AutoProxy_HttpClose AutoProxy_hSession
3168
+ End If
3169
+
3170
+ AutoProxy_Cleanup:
3171
+ On Error GoTo 0
3172
+
3173
+ ' Free any strings received from WinHttp APIs
3174
+ If (AutoProxy_IEProxyConfig.AutoProxy_lpszAutoConfigUrl <> 0) Then
3175
+ AutoProxy_GlobalFree AutoProxy_IEProxyConfig.AutoProxy_lpszAutoConfigUrl
3176
+ AutoProxy_IEProxyConfig.AutoProxy_lpszAutoConfigUrl = 0
3177
+ End If
3178
+ If (AutoProxy_IEProxyConfig.AutoProxy_lpszProxy <> 0) Then
3179
+ AutoProxy_GlobalFree AutoProxy_IEProxyConfig.AutoProxy_lpszProxy
3180
+ AutoProxy_IEProxyConfig.AutoProxy_lpszProxy = 0
3181
+ End If
3182
+ If (AutoProxy_IEProxyConfig.AutoProxy_lpszProxyBypass <> 0) Then
3183
+ AutoProxy_GlobalFree AutoProxy_IEProxyConfig.AutoProxy_lpszProxyBypass
3184
+ AutoProxy_IEProxyConfig.AutoProxy_lpszProxyBypass = 0
3185
+ End If
3186
+ If (AutoProxy_ProxyInfo.AutoProxy_lpszProxy <> 0) Then
3187
+ AutoProxy_GlobalFree AutoProxy_ProxyInfo.AutoProxy_lpszProxy
3188
+ AutoProxy_ProxyInfo.AutoProxy_lpszProxy = 0
3189
+ End If
3190
+ If (AutoProxy_ProxyInfo.AutoProxy_lpszProxyBypass <> 0) Then
3191
+ AutoProxy_GlobalFree AutoProxy_ProxyInfo.AutoProxy_lpszProxyBypass
3192
+ AutoProxy_ProxyInfo.AutoProxy_lpszProxyBypass = 0
3193
+ End If
3194
+
3195
+ ' Error handling
3196
+ If Err.Number <> 0 Then
3197
+ ' Unmanaged error
3198
+ Err.Raise Err.Number, "AutoProxy:" & Err.source, Err.Description, Err.HelpFile, Err.HelpContext
3199
+ ElseIf AutoProxy_Error <> 0 Then
3200
+ Err.Raise AutoProxy_Error, "AutoProxy", AutoProxy_ErrorMsg
3201
+ End If
3202
+ #End If
3203
+ End Sub