Attribute VB_Name = "modTokens"
'---------------------------
' Tokeniser
'------------------------------
Option Explicit
'------------------------------------------------
' Strings can be quoted with double quotation marks.
'-----------------------------------------------------
Public Function GetToken(ByRef s As String) As String
xEatWhiteSpace s
Select Case Left$(s, 1)
Case """"
Dim lX As String
lX = InStr(2, s, """")
If lX = 0 Then
Err.Raise enumErrorCodes.MissingClosingQuotationMark, "modTokens.GetToken", "Expected closing quotation mark"
Else
GetToken = Mid$(s, 2, lX - 2)
s = Mid$(s, lX + 1)
End If
Case "{", "}", "[", "]", ":", ","
GetToken = Left$(s, 1)
s = Mid$(s, 2)
Case Else
Do While (Left$(s, 1) > " ") And (InStr("{}[],", Left$(s, 1)) = 0) And (LenB(s) > 0)
GetToken = GetToken & Left$(s, 1)
s = Mid$(s, 2)
Loop
End Select
End Function
Private Sub xEatWhiteSpace(ByRef s As String)
Do
Do While (Left$(s, 1) <= " ") And (LenB(s) > 0)
s = Mid$(s, 2)
Loop
If (Left$(s, 1) = "#") Then
xEatComment s
Else
Exit Sub
End If
Loop
End Sub
Private Sub xEatComment(ByRef s As String)
Do While (Left$(s, 1) <> vbCr) And (LenB(s) > 0)
s = Mid$(s, 2)
Loop
End Sub
'---------------------------------------------------------
' Read a JSON like structure from the string and return it as a dictionary.
'
' see http://www.crockford.com/JSON/index.html
'A JSON object is an unordered set of name/value pairs.
'The object begins with '{' and ends with '}'.
'Each name is followed by ':' and the name/value pairs are separated by ','.
'A JSON array is an ordered collection of values.
'The collection begins with '[' and ends with ']'. Values are separated by ','.
'A value can be a string in double quotes, or a number, or true or false or null,
'or a JSON object or a JSON array. These structures can be nested.
'A string is a collection of zero or more Unicode characters, wrapped
'in double quotes, using backslash escapes.
'A character is represented as a single character string.
'Whitespace can be inserted between any pair of tokens.
'Annotations can be included using the /* ... */ and // commenting conventions.
' The syntax is:
'JSON -object:
' { property-list }
' {}
'property-list :
' string-literal : value
' property-list , string-literal : value
'value:
' string-literal
' Numeric -literal
' JSON -object
' JSON-array
' true
' false
' null
'JSON-array :
' [ element-list ]
' []
'element -List:
' value
' element -List, value
'
'look at the code for certainty.
' This version does not deal with
' - character escapes so don't try embedding quotation marks.
' - comments
'---------------------------
Public Function ReadJSON(sStream As String) As Dictionary
Dim sToken As String
sToken = GetToken(sStream)
If (sToken = "{") Then
Set ReadJSON = xReadPropertyList(sStream)
Else
Err.Raise enumErrorCodes.ExpectedOpeningCurlyBracket, "ReadDefinitions", "Expected opening curly bracket but got <" & sToken & ">"
End If
End Function
'----------------------------------------------------------
' Leading { has already been eaten here.
'-----------------------------------------------
Public Function xReadPropertyList(sStream As String) As Dictionary
Set xReadPropertyList = New Dictionary
Dim sToken As String
Dim sKey As String
Do
sToken = GetToken(sStream)
Select Case sToken
Case "}" ' finished
Exit Do
Case ","
' simply ignore the comma, personally I think that commas are onions (See 'The Periodic Table')
Case Else ' must be the key
sKey = sToken
sToken = GetToken(sStream) ' must be a colon, ignore it
xReadPropertyList.Add sKey, xReadValue(sStream)
End Select
Loop
End Function
'----------------------------------------------------------
' Leading [ has already been eaten here.
'-----------------------------------------------
Public Function xReadArray(sStream As String) As Collection
Set xReadArray = New Collection
Dim sToken As String
Dim sKey As String
Do
sToken = GetToken(sStream)
Select Case sToken
Case "]" ' finished
Exit Do
Case ","
' simply ignore the comma, personally I think that commas are onions (See 'The Periodic Table')
Case Else ' must be the item
xReadArray.Add sToken ' TODO: convert numbers, trues, false, etc.
End Select
Loop
End Function
'-------------------------------------------------
' Read a value.
' The return type here is variant so that we can return a string or a dictionary
'------------------------------------------------------
Private Function xReadValue(sStream As String) As Variant
Dim sToken As String
sToken = GetToken(sStream)
Select Case sToken
Case "{"
Set xReadValue = xReadPropertyList(sStream)
Case "["
Set xReadValue = xReadArray(sStream)
Case Else ' literal
xReadValue = sToken 'TODO: should convert trues and falses etc.
End Select
End Function
Public Function PropertyListToString(PropertyList As Dictionary) As String
Dim oPropertyList As Dictionary
Dim oArray As Collection
Dim sKey As String
Dim lX As Long
Dim sValue As String
PropertyListToString = "{"
With PropertyList
For lX = 0 To .Count - 1
sKey = .Keys(lX)
If TypeOf .Item(sKey) Is Dictionary Then
sValue = PropertyListToString(.Item(sKey)) & vbCrLf
ElseIf TypeOf .Item(sKey) Is Collection Then
sValue = xArrayToString(.Item(sKey)) & vbCrLf
ElseIf VarType(.Item(sKey)) = vbString Then
sValue = """" & .Item(sKey) & """"
Else
sValue = .Item(sKey)
End If
PropertyListToString = PropertyListToString & """" & sKey & """ : " & sValue
If lX < .Count - 1 Then
PropertyListToString = PropertyListToString & ", "
End If
Next lX
PropertyListToString = PropertyListToString & "}"
End With
End Function
Public Function xArrayToString(ArrayList As Collection) As String
Dim lX As Long
Dim sValue As String
xArrayToString = "["
With ArrayList
For lX = 1 To .Count
If TypeOf .Item(lX) Is Dictionary Then
sValue = PropertyListToString(.Item(lX)) & vbCrLf
ElseIf TypeOf .Item(lX) Is Collection Then
sValue = xArrayToString(.Item(lX)) & vbCrLf
Else
sValue = .Item(lX)
End If
xArrayToString = xArrayToString & sValue
If lX < .Count Then
xArrayToString = xArrayToString & ", "
End If
Next lX
xArrayToString = xArrayToString & "]"
End With
End Function