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.
- tdrpa/_tdxlwings/__init__.py +193 -0
- tdrpa/_tdxlwings/__pycache__/__init__.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/__init__.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/_win32patch.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/_win32patch.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/_xlwindows.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/_xlwindows.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/apps.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/apps.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/base_classes.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/base_classes.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/com_server.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/com_server.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/constants.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/constants.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/expansion.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/expansion.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/main.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/main.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/udfs.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/udfs.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/utils.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/utils.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/_win32patch.py +90 -0
- tdrpa/_tdxlwings/_xlmac.py +2240 -0
- tdrpa/_tdxlwings/_xlwindows.py +2518 -0
- tdrpa/_tdxlwings/addin/Dictionary.cls +474 -0
- tdrpa/_tdxlwings/addin/IWebAuthenticator.cls +71 -0
- tdrpa/_tdxlwings/addin/WebClient.cls +772 -0
- tdrpa/_tdxlwings/addin/WebHelpers.bas +3203 -0
- tdrpa/_tdxlwings/addin/WebRequest.cls +875 -0
- tdrpa/_tdxlwings/addin/WebResponse.cls +453 -0
- tdrpa/_tdxlwings/addin/xlwings.xlam +0 -0
- tdrpa/_tdxlwings/apps.py +35 -0
- tdrpa/_tdxlwings/base_classes.py +1092 -0
- tdrpa/_tdxlwings/cli.py +1306 -0
- tdrpa/_tdxlwings/com_server.py +385 -0
- tdrpa/_tdxlwings/constants.py +3080 -0
- tdrpa/_tdxlwings/conversion/__init__.py +103 -0
- tdrpa/_tdxlwings/conversion/framework.py +147 -0
- tdrpa/_tdxlwings/conversion/numpy_conv.py +34 -0
- tdrpa/_tdxlwings/conversion/pandas_conv.py +184 -0
- tdrpa/_tdxlwings/conversion/standard.py +321 -0
- tdrpa/_tdxlwings/expansion.py +83 -0
- tdrpa/_tdxlwings/ext/__init__.py +3 -0
- tdrpa/_tdxlwings/ext/sql.py +73 -0
- tdrpa/_tdxlwings/html/xlwings-alert.html +71 -0
- tdrpa/_tdxlwings/js/xlwings.js +577 -0
- tdrpa/_tdxlwings/js/xlwings.ts +729 -0
- tdrpa/_tdxlwings/mac_dict.py +6399 -0
- tdrpa/_tdxlwings/main.py +5205 -0
- tdrpa/_tdxlwings/mistune/__init__.py +63 -0
- tdrpa/_tdxlwings/mistune/block_parser.py +366 -0
- tdrpa/_tdxlwings/mistune/inline_parser.py +216 -0
- tdrpa/_tdxlwings/mistune/markdown.py +84 -0
- tdrpa/_tdxlwings/mistune/renderers.py +220 -0
- tdrpa/_tdxlwings/mistune/scanner.py +121 -0
- tdrpa/_tdxlwings/mistune/util.py +41 -0
- tdrpa/_tdxlwings/pro/__init__.py +40 -0
- tdrpa/_tdxlwings/pro/_xlcalamine.py +536 -0
- tdrpa/_tdxlwings/pro/_xlofficejs.py +146 -0
- tdrpa/_tdxlwings/pro/_xlremote.py +1293 -0
- tdrpa/_tdxlwings/pro/custom_functions_code.js +150 -0
- tdrpa/_tdxlwings/pro/embedded_code.py +60 -0
- tdrpa/_tdxlwings/pro/udfs_officejs.py +549 -0
- tdrpa/_tdxlwings/pro/utils.py +199 -0
- tdrpa/_tdxlwings/quickstart.xlsm +0 -0
- tdrpa/_tdxlwings/quickstart_addin.xlam +0 -0
- tdrpa/_tdxlwings/quickstart_addin_ribbon.xlam +0 -0
- tdrpa/_tdxlwings/quickstart_fastapi/main.py +47 -0
- tdrpa/_tdxlwings/quickstart_fastapi/requirements.txt +3 -0
- tdrpa/_tdxlwings/quickstart_standalone.xlsm +0 -0
- tdrpa/_tdxlwings/reports.py +12 -0
- tdrpa/_tdxlwings/rest/__init__.py +1 -0
- tdrpa/_tdxlwings/rest/api.py +368 -0
- tdrpa/_tdxlwings/rest/serializers.py +103 -0
- tdrpa/_tdxlwings/server.py +14 -0
- tdrpa/_tdxlwings/udfs.py +775 -0
- tdrpa/_tdxlwings/utils.py +777 -0
- tdrpa/_tdxlwings/xlwings-0.31.6.applescript +30 -0
- tdrpa/_tdxlwings/xlwings.bas +2061 -0
- tdrpa/_tdxlwings/xlwings_custom_addin.bas +2042 -0
- tdrpa/_tdxlwings/xlwingslib.cp38-win_amd64.pyd +0 -0
- tdrpa/tdworker/__init__.pyi +8 -0
- tdrpa/tdworker/_excel.pyi +703 -0
- tdrpa/tdworker/_img.pyi +173 -0
- tdrpa/tdworker/_os.pyi +46 -0
- tdrpa/tdworker/_w.pyi +129 -0
- tdrpa/tdworker/_web.pyi +248 -0
- tdrpa/tdworker/_winE.pyi +246 -0
- tdrpa/tdworker/_winK.pyi +74 -0
- tdrpa/tdworker/_winM.pyi +117 -0
- tdrpa/tdworker.cp39-win_amd64.pyd +0 -0
- tdrpa.tdworker-1.1.9.3.dist-info/METADATA +25 -0
- tdrpa.tdworker-1.1.9.3.dist-info/RECORD +97 -0
- tdrpa.tdworker-1.1.9.3.dist-info/WHEEL +5 -0
- 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
|