diff --git a/.gitignore b/.gitignore
index bb21aae..95a0227 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,31 @@ src/jsonstringify.ERR
*.zip
*.PJT
*.pjx
+scanner.js
+src/arraytocursor.BAK
+JSONFoxHelper/obj/x86/Release/JSONFoxHelper.pdb
+JSONFoxHelper/obj/x86/Release/JSONFoxHelper.dll
+jsonfox_ref.FPT
+jsonfox_ref.DBF
+jsonfox_ref.CDX
+JSONFoxHelper/.vs/JSONFoxHelper/FileContentIndex/ce6bbe6c-b547-4029-a444-eb439cf20c46.vsidx
+JSONFoxHelper/.vs/JSONFoxHelper/FileContentIndex/read.lock
+JSONFoxHelper/.vs/JSONFoxHelper/v16/.suo
+JSONFoxHelper/.vs/JSONFoxHelper/v17/.suo
+JSONFoxHelper/bin/Debug/JSONFoxHelper.dll
+JSONFoxHelper/bin/Debug/JSONFoxHelper.pdb
+JSONFoxHelper/bin/Debug/JSONFoxHelper.tlb
+JSONFoxHelper/bin/Release/JSONFoxHelper.dll
+JSONFoxHelper/bin/Release/JSONFoxHelper.pdb
+JSONFoxHelper/bin/x86/Debug/JSONFoxHelper.dll
+JSONFoxHelper/bin/x86/Debug/JSONFoxHelper.pdb
+JSONFoxHelper/bin/x86/Debug/JSONFoxHelper.tlb
+JSONFoxHelper/bin/x86/Release/JSONFoxHelper.dll
+JSONFoxHelper/bin/x86/Release/JSONFoxHelper.pdb
+JSONFoxHelper/obj/x86/Release/DesignTimeResolveAssemblyReferences.cache
+JSONFoxHelper/obj/x86/Release/DesignTimeResolveAssemblyReferencesInput.cache
+JSONFoxHelper/obj/x86/Release/JSONFoxHelper.csproj.AssemblyReference.cache
+JSONFoxHelper/obj/x86/Release/JSONFoxHelper.csproj.CoreCompileInputs.cache
+JSONFoxHelper/obj/x86/Release/JSONFoxHelper.csproj.FileListAbsolute.txt
+*.BAK
+*.FXP
diff --git a/JSONFox.PJT b/JSONFox.PJT
index 347ae8a..b656203 100644
Binary files a/JSONFox.PJT and b/JSONFox.PJT differ
diff --git a/JSONFox.pjx b/JSONFox.pjx
index d3af685..7057ad0 100644
Binary files a/JSONFox.pjx and b/JSONFox.pjx differ
diff --git a/README.md b/README.md
index 451efb0..4ce482a 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,10 @@
**JSONFox** is a free **JSON / XML** ***parser*** for Visual FoxPro 9.0
-Si te gusta mi trabajo puedes apoyarme con un donativo:
-[](https://www.paypal.com/donate/?hosted_button_id=LXQYXFP77AD2G)
+Formas de apoyar:
+1. Donativo en Paypal [](https://www.paypal.com/donate/?hosted_button_id=LXQYXFP77AD2G)
+2. Patrocinio en [Patreon](www.patreon.com/IrwinRodriguez)
+3. Seguir este proyecto (es gratis) [Stargazer](https://github.com/Irwin1985/JSONFox/stargazers)
Gracias por tu apoyo!
@@ -66,10 +68,13 @@ Insert into cGames Values('The Legend of Zelda', 1986)
?_Screen.Json.CursorStructure('cGames')
```
## Full Documentation
-*  **_Screen.Json.CursorToJSON(tcCursor As String *[, tbCurrentRow As Boolean [, tnDataSession As Integer]]*)**
+*  **_Screen.Json.CursorToJSON(tcCursor As String *[, tbCurrentRow, tnDataSession, tbJustArray, tbParseUTF8, tbTrimChars]*)**
*  **tcCursor:** the name of your cursor.
*  **tbCurrentRow:** ¿Would you like to serialize the current row? .F. as default.
*  **tnDataSession:** Provide this parameter if you're working in a private session.
+*  **tbJustArray:** if is set to .T. then you get just an array with the data, otherwise you'll get an object containing both the cursor name and the array data.
+*  **tbParseUTF8:** if is set to .T. then all special characters will be encoded. Eg: 'é' => '\u00e9'
+*  **tbTrimChars:** if is set to .T. then all right blank spaces will be trimed.
@@ -205,3 +210,7 @@ _Screen.Json.JSONViewer(lcStr)
```


+
+**SIN GARANTÍA**
+
+EL SOFTWARE SE PROPORCIONA "TAL CUAL", SIN GARANTÍA DE NINGÚN TIPO, EXPRESA O IMPLÍCITA, INCLUYENDO PERO NO LIMITADO A LAS GARANTÍAS DE COMERCIABILIDAD, IDONEIDAD PARA UN PROPÓSITO PARTICULAR Y NO INFRACCIÓN. EN NINGÚN CASO LOS AUTORES O TITULARES DE DERECHOS DE AUTOR SERÁN RESPONSABLES DE NINGÚN RECLAMO, DAÑO U OTRA RESPONSABILIDAD, YA SEA EN UNA ACCIÓN CONTRACTUAL, AGRAVIO O DE OTRA MANERA, QUE SURJA DE, FUERA DE O EN RELACIÓN CON EL SOFTWARE O EL USO U OTRAS NEGOCIOS EN EL SOFTWARE.
diff --git a/jsonfox.app b/jsonfox.app
index 56a373f..716c7c5 100644
Binary files a/jsonfox.app and b/jsonfox.app differ
diff --git a/src/arraytocursor.prg b/src/arraytocursor.prg
index 1b89124..38a6580 100644
--- a/src/arraytocursor.prg
+++ b/src/arraytocursor.prg
@@ -10,10 +10,10 @@ Define Class ArrayToCursor As Session
Dimension aRows(1)
nRowCount = 0
- Dimension tokens[1]
Hidden current
Hidden previous
Hidden peek
+ hidden tokenCollection
Hidden capacity
Hidden length
@@ -22,8 +22,8 @@ Define Class ArrayToCursor As Session
function init(toScanner)
With this
Local laTokens
- laTokens = toScanner.scanTokens()
- =Acopy(laTokens, .tokens)
+ .tokenCollection = toScanner.scanTokens()
+
.current = 1
.oTableStruct = Createobject('Collection')
@@ -59,6 +59,7 @@ Define Class ArrayToCursor As Session
Dimension .aRows[.capacity]
.InsertData()
+ .CleanUp()
endwith
EndFunc
@@ -114,38 +115,48 @@ Define Class ArrayToCursor As Session
&& ======================================================================== &&
Hidden Function kvp
With this
- Local lcProp, lxValue, lcType, lnFieldLength, lcFieldName, loPair
+ Local lcProp, lxValue, lcType, lnFieldLength, lcFieldName, lnDecimals, loPair
.consume(T_STRING, "Expect right key element")
lcProp = .previous.value
-
.consume(T_COLON, "Expect ':' after key element.")
lcFieldName = Space(1)
lxValue = .Value()
- lcType = Vartype(lxValue)
+ lcType = lxValue.Type
lnFieldLength = 0
+ lnDecimals = 0
Do Case
Case lcType == 'N'
- lcType = Iif(Occurs('.', Transform(lxValue)) > 0, 'N', 'I')
+ local lcNumStr
+ lcNumStr = lxValue.Literal
+ lcType = Iif(Occurs('.', lcNumStr) > 0 or lxValue.Value > INTEGER_MAX_CAPACITY, 'N', 'I')
+ && >>>>>>> IRODG 03/17/24
+ If lcType == 'N'
+ lnFieldLength = Len(lcNumStr)
+ lnDecimals = len(GetWordNum(lcNumStr,2,'.'))
+ EndIf
+ && <<<<<<< IRODG 03/17/24
Case lcType == 'C'
- If Len(lxValue) > STRING_MAX_SIZE
+ If Len(lxValue.Value) > STRING_MAX_SIZE
lcType = 'M'
Else
- lxValue = _Screen.JSONUtils.CheckString(lxValue)
- lcType = Vartype(lxValue)
+ lxValue.Value = _Screen.JSONUtils.CheckString(lxValue.Value)
+ lcType = Vartype(lxValue.Value)
Endif
If lcType == 'C'
- lnFieldLength = Iif(Empty(Len(lxValue)), 1, Len(lxValue))
+ lnFieldLength = Iif(Empty(Len(lxValue.Value)), 1, Len(lxValue.Value))
Endif
Endcase
lcFieldName = Lower(_Screen.JSONUtils.CheckProp(lcProp))
- .CheckStructure(lcFieldName, lcType, lnFieldLength)
+ .CheckStructure(lcFieldName, lcType, lnFieldLength, lnDecimals)
* Set Key-Value pair object
loPair = CreateObject("Empty")
=AddProperty(loPair, "field", lcFieldName)
- =AddProperty(loPair, "value", lxValue)
+ =AddProperty(loPair, "value", lxValue.Value)
+
+ release lxValue
Return loPair
EndWith
@@ -156,25 +167,37 @@ Define Class ArrayToCursor As Session
&& ======================================================================== &&
Hidden Function Value As Variant
With this
+ local loToken
+ loToken = createobject("Empty")
+ addproperty(loToken, "type", "")
+ addproperty(loToken, "literal", "")
+ addproperty(loToken, "value", .null.)
+
Do Case
Case .match(T_STRING)
- Return .previous.value
-
+ loToken.type = 'C'
+ loToken.literal = .previous.value
+ loToken.value = .previous.value
case .match(T_NUMBER)
Local lcValue
lcValue = .previous.value
- return iif(at('.', lcValue) > 0, Val(lcValue), int(Val(lcValue)))
+ loToken.type = 'N'
+ loToken.literal = .previous.value
+ loToken.value = iif(at('.', lcValue) > 0, Val(lcValue), int(Val(lcValue)))
case .match(T_BOOLEAN)
- return (.previous.value == 'true')
-
+ loToken.type = 'L'
+ loToken.literal = .previous.value
+ loToken.value = (.previous.value == 'true')
case .match(T_NULL)
- return .null.
-
+ loToken.type = 'X'
+ loToken.literal = 'null'
+ loToken.value = .null.
Otherwise
Error "Parser Error: This token is invalid in for cursor conversion: '" + _screen.jsonUtils.tokenTypeToStr(.peek.type) + "'"
- EndCase
- EndWith
+ endcase
+ endwith
+ return loToken
Endfunc
* InsertData
Hidden Function InsertData
@@ -218,8 +241,8 @@ Define Class ArrayToCursor As Session
loPair.FieldType = 'L'
Case loPair.FieldType == 'C'
lcFlags = '(' + Alltrim(Str(loPair.FieldLength)) + ')'
- Case loPair.FieldType == 'N'
- lcFlags = "(18,5)" && Integer part 18 and Decimal part 5
+ Case loPair.FieldType == 'N'
+ lcFlags = "("+Alltrim(Str(loPair.FieldLength))+","+Alltrim(Str(loPair.FieldDecimals))+")" && Integer part 18 and Decimal part 5
EndCase
cQuery = cQuery + loPair.FieldType + lcFlags + " NULL"
EndFor
@@ -235,9 +258,9 @@ Define Class ArrayToCursor As Session
* CheckStructure
* Adds or Updates an entry key in the oTableStruct dictionary
* ============================================================= *
- Function CheckStructure(tcFieldName, tcType, tnLength)
+ Function CheckStructure(tcFieldName, tcType, tnLength, tnDecimals)
With this
- Local nFieldIdx, loPair
+ Local nFieldIdx, loPair, lbUpdate
nFieldIdx = .oTableStruct.GetKey(tcFieldName)
If nFieldIdx > 0
loPair = .oTableStruct.Item(nFieldIdx)
@@ -256,13 +279,41 @@ Define Class ArrayToCursor As Session
* Remove the key and registry a new one
.oTableStruct.Remove(nFieldIdx)
.oTableStruct.Add(loPair, tcFieldName)
- Endif
+ EndIf
+ && >>>>>>> IRODG 03/17/24
+ case loPair.fieldType == 'N' && Check integer and decimal part length (always saves the longest)
+ If tnLength > loPair.fieldLength
+ loPair.fieldLength = tnLength
+ lbUpdate = .T.
+ EndIf
+ If tnDecimals > loPair.fieldDecimals
+ loPair.fieldDecimals = tnDecimals
+ lbUpdate = .T.
+ EndIf
+ If lbUpdate
+ * Remove the key and registry a new one
+ .oTableStruct.Remove(nFieldIdx)
+ .oTableStruct.Add(loPair, tcFieldName)
+ EndIf
+ Case loPair.fieldType == 'I' and tcType == 'N' && Check string length (always saves the longest)
+ loPair.fieldType = tcType
+ If tnLength > loPair.fieldLength
+ loPair.fieldLength = tnLength
+ EndIf
+ If tnDecimals > loPair.fieldDecimals
+ loPair.fieldDecimals = tnDecimals
+ EndIf
+ * Remove the key and registry a new one
+ .oTableStruct.Remove(nFieldIdx)
+ .oTableStruct.Add(loPair, tcFieldName)
+ && <<<<<<< IRODG 03/17/24
EndCase
Else
* Insert new field.
loPair = CreateObject('Empty')
AddProperty(loPair, 'fieldType', tcType)
AddProperty(loPair, 'fieldLength', tnLength)
+ AddProperty(loPair, 'fieldDecimals', tnDecimals)
.oTableStruct.Add(loPair, tcFieldName)
EndIf
EndWith
@@ -304,7 +355,7 @@ Define Class ArrayToCursor As Session
If !.isAtEnd()
.current = .current + 1
EndIf
- Return .tokens[.current-1]
+ Return .TokenCollection.tokens[.current-1]
EndWith
endfunc
@@ -316,14 +367,28 @@ Define Class ArrayToCursor As Session
Hidden Function peek_access
With this
- Return .tokens[.current]
+ Return .TokenCollection.tokens[.current]
endwith
endfunc
Hidden Function previous_access
With this
- Return .tokens[.current-1]
+ Return .TokenCollection.tokens[.current-1]
EndWith
EndFunc
+ function CleanUp
+ if type('this.aRows',1) == 'A' and alen(this.aRows) > 0
+ local i
+ for i=1 to alen(this.aRows)
+ this.aRows[i] = .null.
+ next
+ dimension this.aRows[1]
+ this.aRows[1] = .null.
+ endif
+ this.oTableStruct = .null.
+ this.length = 1
+ this.capacity = 0
+ endfunc
+
Enddefine
diff --git a/src/cursortoarray.prg b/src/cursortoarray.prg
index 3b414ea..265e8df 100644
--- a/src/cursortoarray.prg
+++ b/src/cursortoarray.prg
@@ -2,6 +2,9 @@
define class CursorToArray as session
nSessionID = 0
CurName = ""
+ ParseUTF8 = .f.
+ TrimChars = .F.
+
* Function CursorToArray
function CursorToArray as memo
if !empty(this.nSessionID)
@@ -9,7 +12,17 @@ define class CursorToArray as session
endif
private JSONUtils
JSONUtils = _screen.JSONUtils
- local lcOutput as memo, i as Integer
+ local lcOutput as memo, ;
+ i as Integer, ;
+ lcValue as Variant, ;
+ llCentury as Boolean, ;
+ llDeleted as Boolean, ;
+ lcDateAct as string, ;
+ nCounter as Integer, ;
+ lnTotField as Integer, ;
+ lnTotal as Integer, ;
+ lnRecNo as Integer
+
lcOutput = "["
llCentury = set("Century") == "OFF"
llDeleted = set("Deleted") == "OFF"
@@ -37,7 +50,7 @@ define class CursorToArray as session
lcValue = evaluate(.CurName + "." + aColumns[i, 1])
if vartype(lcValue) = 'X'
lcValue = "null"
- lcOutput = lcOutput + alltrim(lcValue)
+ lcOutput = lcOutput + lcValue
else
do case
case aColumns[i, 2] $ "CDTBGMQVW"
@@ -54,12 +67,12 @@ define class CursorToArray as session
else
lcValue = 'null'
endif
- otherwise
- lcValue = JSONUtils.GetString(alltrim(lcValue))
+ Otherwise
+ lcValue = JSONUtils.GetString(Iif(this.TrimChars, Alltrim(lcValue), lcValue), this.ParseUTF8)
endcase
- lcOutput = lcOutput + alltrim(lcValue)
+ lcOutput = lcOutput + Iif(this.TrimChars, Alltrim(lcValue), lcValue)
case aColumns[i, 2] $ "YFIN"
- lcOutput = lcOutput + transform(lcValue)
+ lcOutput = lcOutput + alltrim(transform(lcValue, "@T"))
case aColumns[i, 2] = "L"
lcOutput = lcOutput + iif(lcValue, "true", "false")
endcase
diff --git a/src/cursortojsonobject.prg b/src/cursortojsonobject.prg
new file mode 100644
index 0000000..bfb86d8
--- /dev/null
+++ b/src/cursortojsonobject.prg
@@ -0,0 +1,95 @@
+* CursorToJsonObject Parser
+define class CursorToJsonObject as session
+ nSessionID = 0
+ CurName = ""
+ ParseUTF8 = .f.
+ TrimChars = .F.
+
+ * Function CursorToArray
+ function CursorToJsonObject as object
+ if !empty(this.nSessionID)
+ set datasession to this.nSessionID
+ EndIf
+ Local laArray, loRow, lnRecno
+ laArray = createobject("TParserInternalArray")
+
+ select (this.CurName)
+ lnRecno = Recno()
+ Scan
+ Scatter memo name loRow
+ laArray.Push(loRow)
+ EndScan
+
+ Try
+ Go lnRecno
+ Catch
+ EndTry
+ return @laArray.getArray()
+ EndFunc
+
+ * MasterDetail
+ Function MasterDetailToJSON(tcMaster as String, tcDetail as String, tcExpr as String, tcDetailAttribute as String, tnSessionID as Integer) as Object
+ if !empty(tnSessionID)
+ set datasession to tnSessionID
+ ENDIF
+
+ Local laArray, loRow, lnRecno, lbContinue, laDetail, lcMacro, lcCursor,i, lcDetailFields, lcMacro
+ laArray = createobject("TParserInternalArray")
+ ** Set Detail cursor
+ this.nSessionID = tnSessionID
+ lcDetailFields = "*"
+ If At("<", tcDetail) > 0
+ lcDetailFields = Alltrim(strextract(tcDetail,"<",">"))
+ tcDetail = Alltrim(GetWordNum(tcDetail,1,"<"))
+ EndIf
+
+ select (tcMaster)
+ lnRecno = Recno()
+ i = 0
+ scan
+ i = i + 1
+ Scatter memo name loRow
+ laDetail = .null.
+ * Filter detail
+ lcCursor = Sys(2015)
+ try
+ lcMacro = "Select " + lcDetailFields + " from " + tcDetail + " where " + tcExpr + " into cursor " + lcCursor
+ &lcMacro
+ lbContinue = Used(lcCursor)
+ Catch
+ lbContinue = .f.
+ EndTry
+ If !lbContinue
+ Loop
+ EndIf
+
+ If Reccount(lcCursor) > 0
+ this.curName = lcCursor
+ laDetail = this.CursorToJsonObject()
+ Local array laRows[1]
+ Acopy(laDetail, laRows)
+ lcMacro = 'AddProperty(loRow, "'+tcDetailAttribute+'[1]", .null.)'
+ &lcMacro
+ lcMacro = 'Acopy(laRows, loRow.'+tcDetailAttribute+')'
+ &lcMacro
+ Else
+ lcMacro = 'AddProperty(loRow, "'+tcDetailAttribute+'", .null.)'
+ &lcMacro
+ EndIf
+
+ Use in (lcCursor)
+
+ laArray.Push(loRow)
+ EndScan
+
+ Try
+ Go lnRecno in (tcMaster)
+ Catch
+ EndTry
+ IF i>0
+ return @laArray.getArray()
+ ELSE
+ RETURN .null.
+ ENDIF
+ EndFunc
+enddefine
\ No newline at end of file
diff --git a/src/frmjsonviewer.SCT b/src/frmjsonviewer.SCT
index fb8a8e5..e6c05a9 100644
Binary files a/src/frmjsonviewer.SCT and b/src/frmjsonviewer.SCT differ
diff --git a/src/frmjsonviewer.scx b/src/frmjsonviewer.scx
index 0b3951e..6ee9395 100644
Binary files a/src/frmjsonviewer.scx and b/src/frmjsonviewer.scx differ
diff --git a/src/jscriptscanner.prg b/src/jscriptscanner.prg
new file mode 100644
index 0000000..a9e5fba
--- /dev/null
+++ b/src/jscriptscanner.prg
@@ -0,0 +1,284 @@
+#include "JSONFox.h"
+* JScriptScanner
+define class JScriptScanner as custom
+ Hidden source
+ hidden line
+
+ Hidden capacity
+ Hidden length
+
+ Dimension tokens[1]
+ oScript = .null.
+
+ function init(tcSource)
+ With this
+ .length = 1
+ .capacity = 0
+ && IRODG 11/08/2023 Inicio
+ * We remove possible invalid characters from the input source.
+ tcSource = STRTRAN(tcSource, CHR(0))
+ tcSource = STRTRAN(tcSource, CHR(10))
+ tcSource = STRTRAN(tcSource, CHR(13))
+ && IRODG 11/08/2023 Fin
+ .source = tcSource
+ .line = 1
+ endwith
+ endfunc
+
+ function escapeCharacters(tcLexeme)
+ * Convert all escape sequences
+ tcLexeme = Strtran(tcLexeme, '\\', '\')
+ tcLexeme = Strtran(tcLexeme, '\/', '/')
+ tcLexeme = Strtran(tcLexeme, '\n', Chr(10))
+ tcLexeme = Strtran(tcLexeme, '\r', Chr(13))
+ tcLexeme = Strtran(tcLexeme, '\t', Chr(9))
+ tcLexeme = Strtran(tcLexeme, '\"', '"')
+ tcLexeme = Strtran(tcLexeme, "\'", "'")
+ return tcLexeme
+ endfunc
+
+ procedure increaseNewLine
+ this.line = this.line + 1
+ endproc
+
+ function checkUnicodeFormat(tcLexeme)
+ * Look for unicode format
+ ** This conversion is better (in performance) than Regular Expressions.
+ && IRODG 09/10/2023 Inicio
+ local lcUnicode, lcConversion, lbReplace, lnPos
+ lnPos = 1
+ do while .T.
+ lbReplace = .F.
+ lcUnicode = substr(tcLexeme, at('\u', tcLexeme, lnPos), 6)
+ if len(lcUnicode) == 6
+ lbReplace = .T.
+ else
+ lcUnicode = substr(tcLexeme, at('\U', tcLexeme, lnPos), 6)
+ if len(lcUnicode) == 6
+ lbReplace = .T.
+ endif
+ endif
+ if lbReplace
+ tcLexeme = strtran(tcLexeme, lcUnicode, strtran(strconv(lcUnicode,16), chr(0)))
+ else
+ exit
+ endif
+ enddo
+ && IRODG 09/10/2023 Fin
+ return tcLexeme
+ endfunc
+
+ Function scanTokens
+ With this
+ Dimension .tokens[1]
+
+ this.oScript = Createobject([MSScriptcontrol.scriptcontrol.1])
+ this.oScript.Language = "JScript"
+ *this.oScript.AddCode(strconv(filetostr('F:\Desarrollo\GitHub\JSONFox\scanner.js'),11))
+ local lcScript
+ lcScript = this.loadScript()
+
+ * _cliptext = lcScript
+ * messagebox(lcScript)
+
+ this.oScript.AddCode(lcScript)
+ this.oScript.AddObject("oScanner", this)
+ this.oScript.Run("ScanTokens", this.source)
+ .capacity = .length-1
+ * Shrink array
+ Dimension .tokens[.capacity]
+ endwith
+ Return @this.tokens
+ endfunc
+
+ function log(tcContent)
+ ? tcContent
+ strtofile(tcContent + CRLF, 'f:\desarrollo\github\jsonfox\trace.log', 1)
+ endfunc
+
+ function addToken(tnTokenType, tcTokenValue)
+ With this
+ .checkCapacity()
+ local loToken
+ loToken = createobject("Empty")
+ =addproperty(loToken, "type", tnTokenType)
+ =addproperty(loToken, "value", tcTokenValue)
+ =AddProperty(loToken, "line", .line)
+
+ .tokens[.length] = loToken
+ .length = .length + 1
+ EndWith
+ EndFunc
+
+ Hidden function checkCapacity
+ With this
+ If .capacity < .length + 1
+ If Empty(.capacity)
+ .capacity = 8
+ Else
+ .capacity = .capacity * 2
+ EndIf
+ Dimension .tokens[.capacity]
+ EndIf
+ endwith
+ endfunc
+
+ procedure showError(tcCharacter, tnCurrent)
+ local lcMessage
+ lcMessage = "Unknown character ['" + transform(tcCharacter) + "'], ascii: [" + TRANSFORM(ASC(tcCharacter)) + "]"
+ error "SYNTAX ERROR: (" + TRANSFORM(this.line) + ":" + TRANSFORM(tnCurrent) + ")" + lcMessage
+ endproc
+
+ function tokenStr(toToken)
+ local lcType, lcValue
+ lcType = _screen.jsonUtils.tokenTypeToStr(toToken.type)
+ lcValue = alltrim(transform(toToken.value))
+ return "Token(" + lcType + ", '" + lcValue + "') at Line(" + Alltrim(Str(toToken.Line)) + ")"
+ endfunc
+
+ function loadScript
+ local lcScript
+ text to lcScript noshow
+var C_LBRACE = 1
+var C_RBRACE = 2
+var C_LBRACKET = 3
+var C_RBRACKET = 4
+var C_COMMA = 5
+var C_COLON = 6
+var C_NULL = 9
+var C_NUMBER = 10
+var C_STRING = 12
+var C_EOF = 17
+var C_BOOLEAN = 18
+var C_NEWLINE = 19
+
+var Spec = [
+ // --------------------------------------
+ // Whitespace:
+ [/^[ \t\r\f]/, null],
+
+ // --------------------------------------
+ // New line:
+ [/^\n/, C_NEWLINE],
+
+ // --------------------------------------
+ // Keywords
+ [/^\btrue\b/, C_BOOLEAN],
+ [/^\bfalse\b/, C_BOOLEAN],
+ [/^\bnull\b/, C_NULL],
+
+ // --------------------------------------
+ // Symbols
+ [/^\{/, C_LBRACE],
+ [/^\}/, C_RBRACE],
+ [/^\[/, C_LBRACKET],
+ [/^\]/, C_RBRACKET],
+ [/^\:/, C_COLON],
+ [/^\,/, C_COMMA],
+
+ // --------------------------------------
+ // Numbers:
+ [/^-?\d+(,\d{3})*(\.\d+)?([eE][-+]?\d+)?/, C_NUMBER],
+
+ // --------------------------------------
+ // Double quoted string:
+ [/^"/, C_STRING]
+];
+
+var _scannerString;
+var _scannerCursor;
+
+function ScanTokens(source) {
+ _scannerString = source;
+ _scannerCursor = 0; // track the position of each character
+
+ while (_scannerCursor < _scannerString.length) {
+ var token = _getNextToken();
+ if (token == null) {
+ break;
+ }
+ oScanner.AddToken(token.type, token.value);
+ }
+ oScanner.AddToken(C_EOF, '');
+}
+
+function _getNextToken() {
+ if (_scannerCursor >= _scannerString.length) {
+ return null;
+ }
+ var string = _scannerString.slice(_scannerCursor);
+
+ for (var i = 0; i < Spec.length; i++) {
+ var regexp = Spec[i][0];
+ var tokenType = Spec[i][1];
+ var tokenValue = _matchRegEx(regexp, string);
+
+ if (tokenValue == null) {
+ continue;
+ }
+
+ if (tokenType == null) {
+ return _getNextToken();
+ }
+
+ if (tokenType === C_NEWLINE) {
+ oScanner.increaseNewLine();
+ return _getNextToken();
+ }
+ var literal = tokenValue;
+ if (tokenType === C_STRING) {
+ literal = _parseString();
+ }
+
+ return {
+ type: tokenType,
+ value: literal
+ };
+ }
+
+ oScanner.showError(string[0], _scannerCursor);
+}
+
+function _matchRegEx(regexp, string) {
+ var matched = regexp.exec(string);
+ if (matched == null) {
+ return null;
+ }
+ _scannerCursor += matched[0].length;
+ return matched[0];
+}
+
+function _parseString() {
+ var ch = '';
+ var looping = true;
+ var start = _scannerCursor-1;
+ var pn = '';
+ while (_scannerCursor < _scannerString.length) {
+ ch = _scannerString.charAt(_scannerCursor);
+ switch (ch) {
+ case '\\':
+ pn = (_scannerCursor+1 <= _scannerString.length) ? _scannerString.charAt(_scannerCursor+1) : '';
+ if (pn === '\\' || pn === '/' || pn === 'n' || pn === 'r' || pn === 't' || pn === '"' || pn === "'") {
+ _scannerCursor++;
+ }
+ break;
+ case '"':
+ looping = false;
+ break;
+ default:
+ break;
+ }
+ _scannerCursor++;
+ if (!looping) {
+ break;
+ }
+ }
+ var lexeme = _scannerString.slice(start+1, _scannerCursor-1);
+ lexeme = oScanner.escapeCharacters(lexeme);
+ lexeme = oScanner.checkUnicodeFormat(lexeme);
+ return lexeme;
+}
+ endtext
+ return lcScript
+ endfunc
+enddefine
\ No newline at end of file
diff --git a/src/jsonclass.prg b/src/jsonclass.prg
index 06a4a55..006c9fe 100644
--- a/src/jsonclass.prg
+++ b/src/jsonclass.prg
@@ -4,16 +4,20 @@ define class JSONClass as session
LastErrorText = ""
lError = .f.
lShowErrors = .t.
- version = "9.7"
+ version = "12.2"
hidden lInternal
hidden lTablePrompt
- Dimension aCustomArray[1]
- && >>>>>>> IRODG 07/01/21
- * Set this property to .T. if you want the lexer uses JSONFoxHelper.dll
+ dimension aCustomArray[1]
+&& >>>>>>> IRODG 07/01/21
+* Set this property to .T. if you want the lexer uses JSONFoxHelper.dll
NETScanner = .f.
- && <<<<<<< IRODG 07/01/21
+&& <<<<<<< IRODG 07/01/21
- *Function Init
+&& >>>>>>> IRODG 02/27/24
+ JScriptScanner = .f.
+&& <<<<<<< IRODG 02/27/24
+
+*Function Init
function init
with this
.ResetError()
@@ -22,90 +26,150 @@ define class JSONClass as session
endwith
endfunc
- * Parse the string text as JSON
+* Parse the string text as JSON
function Parse as memo
lparameters tcJsonStr as memo
- local loJSONObj
+ local loJSONObj&&, loEnv
loJSONObj = .null.
- Dimension this.aCustomArray[1]
- this.aCustomArray[1] = .Null.
+ dimension this.aCustomArray[1]
+ this.aCustomArray[1] = .null.
try
+ &&loEnv = this.saveEnvironment()
this.ResetError()
local lexer, parser
- if this.NETScanner
+ do case
+ case this.NETScanner
lexer = createobject("NetScanner", tcJsonStr)
- else
+ case this.JScriptScanner
+ lexer = createobject("JScriptScanner", tcJsonStr)
+ otherwise
lexer = createobject("Tokenizer", tcJsonStr)
- endif
+ endcase
parser = createobject("Parser", lexer)
loJSONObj = parser.Parse()
-
catch to loEx
+ if type('lexer') == 'O'
+ lexer.CleanUp()
+ endif
+
+ if type('parser') == 'O'
+ parser.CleanUp()
+ endif
this.ShowExceptionError(loEx)
finally
+ &&this.restoreEnvironment(loEnv)
store .null. to lexer, parser
release lexer, parser
- ENDTRY
- If Type('loJSONObj', 1) == 'A'
- Local i
- For i = 1 to Alen(loJSONObj, 1)
- Dimension this.aCustomArray[i]
+ endtry
+ if type('loJSONObj', 1) == 'A'
+ local i
+ for i = 1 to alen(loJSONObj, 1)
+ dimension this.aCustomArray[i]
this.aCustomArray[i] = loJSONObj[i]
endfor
return @this.aCustomArray
- Else
+ else
return loJSONObj
- EndIf
+ endif
endfunc
- * Stringify
+* tokenize
+ function dumpTokens
+ lparameters tcJsonStr as memo, tcOutput as string
+ &&local loEnv
+ try
+ &&loEnv = this.saveEnvironment()
+ this.ResetError()
+ local lexer, nativeScanner
+ do case
+ case this.NETScanner
+ lexer = createobject("NetScanner", tcJsonStr)
+ case this.JScriptScanner
+ lexer = createobject("JScriptScanner", tcJsonStr)
+ otherwise
+ nativeScanner = .t.
+ lexer = createobject("Tokenizer", tcJsonStr)
+ endcase
+ local laTokenCollection
+ laTokenCollection = lexer.scanTokens()
+ if file(tcOutput)
+ delete file (tcOutput)
+ endif
+ for each loToken in laTokenCollection.Tokens
+ strtofile(tokenStr(loToken), tcOutput, 1)
+ endfor
+ catch to loEx
+ if type('lexer') == 'O' and nativeScanner
+ lexer.CleanUp()
+ endif
+ this.ShowExceptionError(loEx)
+ finally
+ &&this.restoreEnvironment(loEnv)
+ release lexer, laTokenCollection
+ endtry
+ endfunc
+
+* Stringify
function Stringify as memo
- lparameters tvNewVal as Variant, tcFlags as string, tlParseUtf8
+ lparameters tvNewVal as Variant, tcFlags as string, tlParseUtf8 as Boolean, tlTrimChars as Boolean
this.ResetError()
- local llParseUtf8, lcTypeFlag, loJSONStr as memo
+ local llParseUtf8, lcTypeFlag, loJSONStr as memo&&, loEnv
lcTypeFlag = type('tcFlags')
llParseUtf8 = iif(lcTypeFlag = 'L', tcFlags, tlParseUtf8)
loJSONStr = ""
-
if vartype(tvNewVal) = "O"
try
+ &&loEnv = this.saveEnvironment()
local objToJson
objToJson = createobject("ObjectToJson")
tvNewVal = objToJson.Encode(@tvNewVal, iif(lcTypeFlag != 'C', .f., tcFlags))
catch to loEx
this.ShowExceptionError(loEx)
finally
+ &&this.restoreEnvironment(loEnv)
objToJson = .null.
release objToJson
endtry
endif
-
try
+ &&loEnv = this.saveEnvironment()
local lexer, parser
lexer = createobject("Tokenizer", tvNewVal)
parser = createobject("JSONStringify", lexer)
- loJSONStr = parser.Stringify(llParseUtf8)
+ loJSONStr = parser.Stringify(llParseUtf8, tlTrimChars)
catch to loEx
+ if type('lexer') == 'O'
+ lexer.CleanUp()
+ endif
+
+ if type('parser') == 'O'
+ parser.CleanUp()
+ endif
this.ShowExceptionError(loEx)
finally
+ &&this.restoreEnvironment(loEnv)
store .null. to lexer, parser
release lexer, parser
endtry
return loJSONStr
endfunc
- * JSONToRTF
+* JSONToRTF
function JSONToRTF as memo
lparameters tvNewVal as Variant, tnIndent as Boolean
+ &&local loEnv
+
this.ResetError()
if vartype(tvNewVal) = 'O'
try
+ &&loEnv = this.saveEnvironment()
local objToJson
objToJson = createobject("ObjectToJson")
tvNewVal = objToJson.Encode(@tvNewVal)
catch to loEx
this.ShowExceptionError(loEx)
finally
+ &&this.restoreEnvironment(loEnv)
objToJson = .null.
release objToJson
endtry
@@ -113,6 +177,7 @@ define class JSONClass as session
local loJSONStr as memo
loJSONStr = ''
try
+ &&loEnv = this.saveEnvironment()
this.lError = .f.
this.LastErrorText = ''
local lexer, parser
@@ -123,17 +188,25 @@ define class JSONClass as session
this.lError = parser.lError
this.LastErrorText = parser.cErrorMsg
catch to loEx
+ if type('lexer') == 'O'
+ lexer.CleanUp()
+ endif
+
+ if type('parser') == 'O'
+ parser.CleanUp()
+ endif
this.ShowExceptionError(loEx)
this.lError = .t.
this.LastErrorText = loEx.message
finally
+ &&this.restoreEnvironment(loEnv)
store .null. to lexer, parser
release lexer, parser
endtry
return loJSONStr
endfunc
- * JSONViewer
+* JSONViewer
function JSONViewer as Void
lparameters tcJsonStr as memo, tlStopExecution as Boolean
do form frmJSONViewer with tcJsonStr, tlStopExecution
@@ -141,50 +214,93 @@ define class JSONClass as session
read events
endif
endfunc
- * ====================== Old JSONFox Functions =========================== *
- * . . . . . . . . . . For backward compatibility . . . . . . . . . . . .
- * ======================================================================== *
- && ======================================================================== &&
- && Function Encode
- && <> please use Stringify function instead.
- && ======================================================================== &&
- function Encode(toObj as object, tcFlags as string) as memo
- local loEncode
- loEncode = createobject("ObjectToJson")
- return loEncode.Encode(@toObj, tcFlags)
+* ====================== Old JSONFox Functions =========================== *
+* . . . . . . . . . . For backward compatibility . . . . . . . . . . . .
+* ======================================================================== *
+&& ======================================================================== &&
+&& Function Encode
+&& <> please use Stringify function instead.
+&& ======================================================================== &&
+ function Encode(toObj as object, tcFlags as string, tlUtf8 as Boolean, tlTrimChars as Boolean) as memo
+ try
+ &&local loEnv
+ &&loEnv = this.saveEnvironment()
+
+ this.ResetError()
+
+ local loEncode, loResult
+ loEncode = createobject("ObjectToJson")
+ loResult = loEncode.Encode(@toObj, tcFlags, tlUtf8, tlTrimChars)
+ catch to loEx
+ this.ShowExceptionError(loEx)
+ this.lError = .t.
+ this.LastErrorText = loEx.message
+ finally
+ &&this.restoreEnvironment(loEnv)
+ loEncode = null
+ release loEncode
+ endtry
+ return loResult
endfunc
- && ======================================================================== &&
- && Function decode
- && <> please use Parse function instead.
- && ======================================================================== &&
+&& ======================================================================== &&
+&& Function decode
+&& <> please use Parse function instead.
+&& ======================================================================== &&
function Decode(tcJsonStr as memo) as object
- return this.Parse(tcJsonStr)
+ try
+ &&local loEnv, loResult
+ &&loEnv = this.saveEnvironment()
+ this.ResetError()
+ loResult = this.Parse(tcJsonStr)
+ catch to loEx
+ this.ShowExceptionError(loEx)
+ this.lError = .t.
+ this.LastErrorText = loEx.message
+ finally
+ &&this.restoreEnvironment(loEnv)
+ endtry
+ return loResult
endfunc
- && ======================================================================== &&
- && Function LoadFile
- && <> please use Parse function instead.
- && ======================================================================== &&
+&& ======================================================================== &&
+&& Function LoadFile
+&& <> please use Parse function instead.
+&& ======================================================================== &&
function LoadFile(tcJsonFile as string) as object
- return this.Decode(filetostr(tcJsonFile))
+ try
+ &&local loEnv, loResult
+ &&loEnv = this.saveEnvironment()
+ this.ResetError()
+ loResult = this.Decode(filetostr(tcJsonFile))
+ catch to loEx
+ this.ShowExceptionError(loEx)
+ this.lError = .t.
+ this.LastErrorText = loEx.message
+ finally
+ &&this.restoreEnvironment(loEnv)
+ endtry
+ return loResult
endfunc
- * ArrayToXML
+* ArrayToXML
function ArrayToXML(tcArray as memo) as string
- local lcOut as string, lcCursor
+ local lcOut as string, lcCursor&&, loEnv
lcOut = ''
- lcCursor = SYS(2015)
+ lcCursor = sys(2015)
if vartype(tcArray) = 'O'
try
+ &&loEnv = this.saveEnvironment()
local objToJson
objToJson = createobject("ObjectToJson")
tcArray = objToJson.Encode(@tcArray)
catch to loEx
this.ShowExceptionError(loEx)
finally
+ &&this.restoreEnvironment(loEnv)
objToJson = .null.
release objToJson
endtry
endif
try
+ &&loEnv = this.saveEnvironment()
this.jsonToCursor(tcArray, lcCursor, set("Datasession"))
if used(lcCursor)
=cursortoxml(lcCursor, 'lcOut', 1, 0, 0, '1')
@@ -192,37 +308,43 @@ define class JSONClass as session
catch to loEx
this.ShowExceptionError(loEx)
finally
+ &&this.restoreEnvironment(loEnv)
use in (select(lcCursor))
endtry
return lcOut
endfunc
- * XMLToJson
+* XMLToJson
function XMLToJson(tcXML as memo) as memo
- local lcJsonXML as memo, loParser
+ local lcJsonXML as memo, loParser&&, loEnv
lcJsonXML = ''
try
+ &&loEnv = this.saveEnvironment()
this.ResetError()
=xmltocursor(tcXML, 'qXML')
loParser = createobject("CursorToArray")
- loParser.CurName = "qXML"
+ loParser.CurName = "qXML"
loParser.nSessionID = set("Datasession")
+ loParser.ParseUTF8 = .t.
+ loParser.TrimChars = .t.
lcJsonXML = loParser.CursorToArray()
catch to loEx
this.ShowExceptionError(loEx)
finally
+ &&this.restoreEnvironment(loEnv)
loParser = .null.
release loParser
use in (select("qXML"))
endtry
return lcJsonXML
endfunc
- * CursorToJSON
+* CursorToJSON
function CursorToJSON as memo
- lparameters tcCursor as string, tbCurrentRow as Boolean, tnDataSession as integer, tlJustArray as Boolean
- local lcJsonXML as memo, loParser, lcCursor
+ lparameters tcCursor as string, tbCurrentRow as Boolean, tnDataSession as integer, tlJustArray as Boolean, tlParseUtf8 as Boolean, tlTrimChars as Boolean
+ local lcJsonXML as memo, loParser, lcCursor&&, loEnv
lcJsonXML = ''
- lcCursor = SYS(2015)
+ lcCursor = sys(2015)
try
+ &&loEnv = this.saveEnvironment()
this.ResetError()
tcCursor = evl(tcCursor, alias())
tnDataSession = evl(tnDataSession, set("Datasession"))
@@ -236,10 +358,17 @@ define class JSONClass as session
loParser = createobject("CursorToArray")
loParser.CurName = lcCursor
loParser.nSessionID = tnDataSession
+&& IRODG 07/10/2023 Inicio
+ loParser.ParseUTF8 = tlParseUtf8
+&& IRODG 07/10/2023 Fin
+&& IRODG 27/10/2023 Inicio
+ loParser.TrimChars = tlTrimChars
+&& IRODG 27/10/2023 Fin
lcJsonXML = loParser.CursorToArray()
catch to loEx
this.ShowExceptionError(loEx)
finally
+ &&this.restoreEnvironment(loEnv)
loParser = .null.
release loParser
use in (select(lcCursor))
@@ -247,11 +376,75 @@ define class JSONClass as session
lcOutput = iif(tlJustArray, lcJsonXML, '{"' + lower(alltrim(tcCursor)) + '":' + lcJsonXML + '}')
return lcOutput
endfunc
- * JSONToCursor
+
+* CursorToJSONObject
+ function CursorToJSONObject(tcCursor as string, tbCurrentRow as Boolean, tnDataSession as integer) as object
+ local loParser, lcCursor, lnRecno, loResult as Variant&&, loEnv
+ lcCursor = sys(2015)
+ try
+ &&loEnv = this.saveEnvironment()
+ this.ResetError()
+ tcCursor = evl(tcCursor, alias())
+ tnDataSession = evl(tnDataSession, set("Datasession"))
+ set datasession to tnDataSession
+ if tbCurrentRow
+ lnRecno = recno(tcCursor)
+ select * from (tcCursor) where recno() = lnRecno into cursor (lcCursor)
+ else
+ select * from (tcCursor) into cursor (lcCursor)
+ endif
+ loParser = createobject("CursorToJsonObject")
+ loParser.CurName = lcCursor
+ loParser.nSessionID = tnDataSession
+ loResult = loParser.CursorToJSONObject()
+ catch to loEx
+ this.ShowExceptionError(loEx)
+ finally
+ &&this.restoreEnvironment(loEnv)
+ loParser = .null.
+ release loParser
+ use in (select(lcCursor))
+ endtry
+
+ if type('loResult', 1) == 'A'
+ local i
+ for i = 1 to alen(loResult, 1)
+ dimension this.aCustomArray[i]
+ this.aCustomArray[i] = loResult[i]
+ endfor
+ return @this.aCustomArray
+ else
+ return loResult
+ endif
+ endfunc
+
+ function MasterDetailToJSON(tcMaster as string, tcDetail as string, tcExpr as string, tcDetailAttribute as string, tnSessionID as integer)
+ local loClass, loResult, lcResult&&, loEnv
+ try
+ &&loEnv = this.saveEnvironment()
+ this.ResetError()
+ tnSessionID = evl(tnSessionID, set("Datasession"))
+ set datasession to tnSessionID
+ loClass = createobject("CursorToJsonObject")
+ loResult = loClass.MasterDetailToJSON(tcMaster, tcDetail, tcExpr, tcDetailAttribute, tnSessionID)
+ catch to loEx
+ this.ShowExceptionError(loEx)
+ finally
+ &&this.restoreEnvironment(loEnv)
+ loClass = .null.
+ release loClass
+ endtry
+*lcResult = this.stringify(@loResult)
+ lcResult = this.Encode(@loResult, "", .t., .t.)
+ return lcResult
+ endfunc
+
+* JSONToCursor
function jsonToCursor(tcJsonStr as memo, tcCursor as string, tnDataSession as integer) as Void
try
- local lexer, parser
+ local lexer, parser, loEnv
this.ResetError()
+ loEnv = this.saveEnvironment()
if !empty(tcCursor)
tnDataSession = evl(tnDataSession, set("Datasession"))
lexer = createobject("Tokenizer", tcJsonStr)
@@ -265,18 +458,27 @@ define class JSONClass as session
endif
endif
catch to loEx
+ if type('lexer') == 'O'
+ lexer.CleanUp()
+ endif
+
+ if type('parser') == 'O'
+ parser.CleanUp()
+ endif
this.ShowExceptionError(loEx)
finally
+ this.restoreEnvironment(loEnv)
store .null. to lexer, parser
release lexer, parser
endtry
endfunc
- * CursorStructure
+* CursorStructure
function CursorStructure
- lparameters tcCursor as string, tnDataSession as integer, tlCopyExtended as Boolean, tlJustArray As Boolean
- local lcOutput as memo
+ lparameters tcCursor as string, tnDataSession as integer, tlCopyExtended as Boolean, tlJustArray as Boolean
+ local lcOutput as memo, loEnv
lcOutput = ''
try
+ loEnv = this.saveEnvironment()
this.ResetError()
loStructureToJSON = createobject("StructureToJSON")
tcCursor = evl(tcCursor, alias())
@@ -288,31 +490,39 @@ define class JSONClass as session
lcOutput = loStructureToJSON.StructureToJSON()
catch to loEx
this.ShowExceptionError(loEx)
+ finally
+ this.restoreEnvironment(loEnv)
endtry
return lcOutput
endfunc
- * tokenize
- function dumpTokens
+* tokenize
+ function dumpTokens2
lparameters tcJsonStr as memo
+ &&local loEnv
try
this.ResetError()
- local loLexer, laTokens, lcTokens as memo, i
+ &&loEnv = this.saveEnvironment()
+ local loLexer, laTokenCollection, lcTokens as memo, i
loLexer = createobject("Tokenizer", tcJsonStr)
- laTokens = loLexer.scanTokens()
+ laTokenCollection = loLexer.scanTokens()
lcTokens = ''
- For i = 1 to Alen(laTokens)
- lcTokens = lcTokens + loLexer.tokenStr(laTokens[i]) + CHR(13) + CHR(10)
+ for i = 1 to alen(laTokenCollection.Tokens)
+ lcTokens = lcTokens + loLexer.tokenStr(laTokenCollection.Tokens[i]) + chr(13) + chr(10)
endfor
catch to loEx
+ if type('lexer') == 'O'
+ lexer.CleanUp()
+ endif
this.ShowExceptionError(loEx)
finally
+ &&this.restoreEnvironment(loEnv)
store .null. to lexer, parser
release lexer, parser
endtry
_cliptext = lcTokens
return lcTokens
endfunc
- * LastErrorText_Assign
+* LastErrorText_Assign
function LastErrorText_Assign
lparameters vNewVal
with this
@@ -322,7 +532,7 @@ define class JSONClass as session
endif
endwith
endfunc
- * ShowExceptionError
+* ShowExceptionError
function ShowExceptionError(toEx as exception) as Void
with this
.lError = .t.
@@ -336,11 +546,11 @@ define class JSONClass as session
.LastErrorText = toEx.message
endwith
endfunc
- * ResetError
+* ResetError
hidden function ResetError as Void
this.lError = .f.
endfunc
- * Destroy
+* Destroy
function destroy
try
if this.lTablePrompt
@@ -349,7 +559,7 @@ define class JSONClass as session
endif
catch
endtry
- && >>>>>>> IRODG 12/28/21
+&& >>>>>>> IRODG 12/28/21
try
removeproperty(_screen, 'json')
catch
@@ -366,6 +576,30 @@ define class JSONClass as session
removeproperty(_screen, 'toml')
catch
endtry
- && <<<<<<< IRODG 12/28/21
+&& <<<<<<< IRODG 12/28/21
endfunc
-enddefine
+
+ protected function saveEnvironment
+ try
+ local loEnv
+ loEnv = createobject("Collection")
+
+ loEnv.add(set("POINT"), "point")
+ loEnv.add(set("SEPARATOR"), "separator")
+
+ set point to '.'
+ set separator to ','
+ catch
+ endtry
+
+ return loEnv
+ endproc
+
+ protected procedure restoreEnvironment(toEnv as collection)
+ try
+ set point to toEnv("point")
+ set separator to toEnv("separator")
+ catch
+ endtry
+ endproc
+enddefine
\ No newline at end of file
diff --git a/src/jsonfox.h b/src/jsonfox.h
index bd34f50..9175648 100644
--- a/src/jsonfox.h
+++ b/src/jsonfox.h
@@ -23,4 +23,5 @@
#Define CR Chr(13)
#Define LF Chr(10)
#Define CRLF CR + LF
-#Define T_TAB Chr(9)
\ No newline at end of file
+#Define T_TAB Chr(9)
+#Define INTEGER_MAX_CAPACITY 2147483647
\ No newline at end of file
diff --git a/src/jsonstringify.prg b/src/jsonstringify.prg
index d57a609..556c4f8 100644
--- a/src/jsonstringify.prg
+++ b/src/jsonstringify.prg
@@ -10,24 +10,29 @@
define class JSONStringify as custom
ParseUtf8 = .f.
+ TrimChars = .f.
- Dimension tokens[1]
Hidden current
Hidden previous
Hidden peek
+ hidden tokenCollection
function init(toScanner)
- Local laTokens
- laTokens = toScanner.scanTokens()
- =Acopy(laTokens, this.tokens)
+ this.tokenCollection = toScanner.scanTokens()
this.current = 1
endfunc
* Stringify
function Stringify as memo
- lparameters tlParseUtf8
+ lparameters tlParseUtf8, tlTrimChars
+ local lcFormatedJson
this.ParseUtf8 = tlParseUtf8
- return this.value(0)
+ this.TrimChars = tlTrimChars
+
+ lcFormatedJson = this.value(0)
+ this.CleanUp()
+
+ return lcFormatedJson
endfunc
&& ======================================================================== &&
&& Function Object
@@ -62,9 +67,10 @@ define class JSONStringify as custom
lparameters tnSpaceIdent as integer
local lcProp as string
this.consume(T_STRING, "Expect right key element")
- lcProp = this.previous.value
- this.consume(T_COLON, "Expect ':' after key element.")
- return '"' + lcProp + '": ' + this.value(tnSpaceIdent)
+ lcProp = _screen.JSONUtils.GetString(this.previous.value, this.ParseUtf8)
+ this.consume(T_COLON, "Expect ':' after key element.")
+ *return '"' + lcProp + '": ' + this.value(tnSpaceIdent)
+ return lcProp + ': ' + this.value(tnSpaceIdent)
endfunc
&& ======================================================================== &&
&& Function Value
@@ -73,7 +79,7 @@ define class JSONStringify as custom
hidden function value(tnSpaceBlock)
do case
case this.match(T_STRING)
- return _screen.JSONUtils.GetString(this.previous.value, this.ParseUtf8)
+ return _screen.JSONUtils.GetString(Iif(this.TrimChars, Alltrim(this.previous.value), this.previous.value), this.ParseUtf8)
case this.match(T_NUMBER)
return this.previous.value
@@ -156,7 +162,7 @@ define class JSONStringify as custom
If !this.isAtEnd()
this.current = this.current + 1
EndIf
- Return this.tokens[this.current-1]
+ Return this.tokenCollection.tokens[this.current-1]
endfunc
Hidden Function isAtEnd
@@ -164,11 +170,20 @@ define class JSONStringify as custom
endfunc
Hidden Function peek_access
- Return this.tokens[this.current]
+ Return this.tokenCollection.tokens[this.current]
endfunc
Hidden Function previous_access
- Return this.tokens[this.current-1]
+ Return this.tokenCollection.tokens[this.current-1]
EndFunc
+ function CleanUp
+ with this
+ .TokenCollection = .null.
+
+ .current = 0
+ .previous = .null.
+ .peek = 0
+ endwith
+ endfunc
enddefine
diff --git a/src/jsontortf.prg b/src/jsontortf.prg
index f404cb0..fcc86d4 100644
--- a/src/jsontortf.prg
+++ b/src/jsontortf.prg
@@ -13,20 +13,17 @@ define class JSONToRTF as custom
lError = .f.
cErrorMsg = ""
-
- Dimension tokens[1]
Hidden current
Hidden previous
Hidden peek
+ hidden tokenCollection
&& ======================================================================== &&
&& Function Init
&& ======================================================================== &&
function init(toScanner)
this.lError = .f.
- Local laTokens
- laTokens = toScanner.scanTokens()
- =Acopy(laTokens, this.tokens)
+ this.tokenCollection = toScanner.scanTokens()
this.current = 1
endfunc
@@ -176,7 +173,7 @@ define class JSONToRTF as custom
If !this.isAtEnd()
this.current = this.current + 1
EndIf
- Return this.tokens[this.current-1]
+ Return this.tokenCollection.tokens[this.current-1]
endfunc
Hidden Function isAtEnd
@@ -184,10 +181,20 @@ define class JSONToRTF as custom
endfunc
Hidden Function peek_access
- Return this.tokens[this.current]
+ Return this.tokenCollection.tokens[this.current]
endfunc
Hidden Function previous_access
- Return this.tokens[this.current-1]
+ Return this.tokenCollection.tokens[this.current-1]
EndFunc
+
+ function CleanUp
+ with this
+ .TokenCollection = .null.
+
+ .current = 0
+ .previous = .null.
+ .peek = 0
+ endwith
+ endfunc
enddefine
diff --git a/src/jsonutils.prg b/src/jsonutils.prg
index 5b81cc1..211d528 100644
--- a/src/jsonutils.prg
+++ b/src/jsonutils.prg
@@ -4,34 +4,55 @@
&& JSON Utilities
&& ======================================================================== &&
define class jsonutils as custom
-
- Dimension aPattern[5, 2]
-
- Function init
+
+ dimension aPattern[8, 2]
+
+ function init
+&& Match a date format in the following pattern
+&& "YYYY-MM-DD"
this.aPattern[1,1] = "^\d\d\d\d-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01])$"
this.aPattern[1,2] = .f.
-
+
+&& Match a date and time format in the following pattern
+&& "YYYY-MM-DD HH:MM:SS"
this.aPattern[2,1] = "^\d\d\d\d-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01]) (00|0?[0-9]|1[0-9]|2[0-3]):([0-9]|[0-5][0-9]):([0-9]|[0-5][0-9])$"
this.aPattern[2,2] = .f.
-
+
+&& Match ISO 8601 date and time formats that include a time zone offset
+&& "YYYY-MM-DDTHH:MM:SSZ" OR "YYYY-MM-DDTHH:MM:SS+HH:MM" OR "YYYY-MM-DDTHH:MM:SS-HH:MM"
this.aPattern[3,1] = "^(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})(\:(\d{2}))?(Z|[+-](\d{2})\:(\d{2}))?$"
this.aPattern[3,2] = .f.
-
+
+&& Match a date and time format in ISO 8601 combined with a single-character time zone identifier
+&& "YYYY-MM-DDTHH:MM(:SS)?.SSS(W)"
this.aPattern[4,1] = "^(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})(\:(\d{2}))?[.](\d{3})(\w{1})$"
this.aPattern[4,2] = .f.
+&& "DD/MM/YYYY" OR "DD-MM-YYYY"
this.aPattern[5,1] = "^([0-2][0-9]|(3)[0-1])[\/-](((0)[0-9])|((1)[0-2]))[\/-]\d{4}$"
this.aPattern[5,2] = .t.
+&& "DD/MM/YYYY HH:MM:SS" or "DD-MM-YYYY HH:MM:SS"
+ this.aPattern[06,1] = "^([0-2][0-9]|(3)[0-1])[\/-](((0)[0-9])|((1)[0-2]))[\/-]\d{4} (00|0?[0-9]|1[0-9]|2[0-3]):([0-9]|[0-5][0-9]):([0-9]|[0-5][0-9])$"
+ this.aPattern[06,2] = .t.
+
+&& "DD/MM/YY" or "DD-MM-YY"
+ this.aPattern[07,1] = "^([0-2][0-9]|(3)[0-1])[\/-](((0)[0-9])|((1)[0-2]))[\/-]\d{2}$"
+ this.aPattern[07,2] = .t.
+
+&& "DD/MM/YY HH:MM:SS" or "DD-MM-YY HH:MM:SS"
+ this.aPattern[08,1] = "^([0-2][0-9]|(3)[0-1])[\/-](((0)[0-9])|((1)[0-2]))[\/-]\d{2} (00|0?[0-9]|1[0-9]|2[0-3]):([0-9]|[0-5][0-9]):([0-9]|[0-5][0-9])$"
+ this.aPattern[08,2] = .t.
+
_screen.oRegEx.global = .t.
- EndFunc
+ endfunc
- && ======================================================================== &&
- && Function GetValue
- && ======================================================================== &&
- function getvalue as string
- lparameters tcvalue as string, tctype as character
+&& ======================================================================== &&
+&& Function GetValue
+&& ======================================================================== &&
+ function getValue as string
+ lparameters tcvalue as string, tctype as character, tlParseUTF8 as Boolean, tlTrimChars as Boolean
do case
case tctype $ "CDTBGMQVWX"
do case
@@ -39,48 +60,59 @@ define class jsonutils as custom
tcvalue = '"' + strtran(dtoc(tcvalue), '.', '-') + '"'
case tctype == 'T'
tcvalue = '"' + strtran(ttoc(tcvalue), '.', '-') + '"'
- Case tctype == 'X'
+ case tctype == 'X'
tcvalue = "null"
- Otherwise
- tcvalue = this.getstring(tcvalue)
+ otherwise
+ tcvalue = this.getString(iif(tlTrimChars, alltrim(tcvalue), tcvalue), tlParseUTF8)
endcase
- tcvalue = alltrim(tcvalue)
case tctype $ "YFIN"
- tcvalue = strtran(transform(tcvalue), ',', '.')
+ if this.HasDecimals(tcvalue)
+ tcvalue = strtran(alltrim(transform(tcvalue, "@T")), ',', '.')
+ else
+ tcvalue = strtran(alltrim(transform(tcvalue)), ',', '.')
+ endif
case tctype == 'L'
tcvalue = iif(tcvalue, "true", "false")
endcase
return tcvalue
endfunc
- && ======================================================================== &&
- && Function CheckString
- && Check the string content in case it is a date or datetime.
- && String itself or string date / datetime format.
- && ======================================================================== &&
+
+ function HasDecimals(tnValue, tnTolerance)
+ if pcount() < 2
+ tnTolerance = 0.0000001
+ endif
+ return abs(tnValue - int(tnValue)) > tnTolerance
+ endfunc
+
+&& ======================================================================== &&
+&& Function CheckString
+&& Check the string content in case it is a date or datetime.
+&& String itself or string date / datetime format.
+&& ======================================================================== &&
function CheckString(tcString)
- If !IsDigit(Left(tcString, 1)) and !IsDigit(Right(tcString, 1))
- Return tcString
- EndIf
- * We try to identify a date format
- Local i
- For i = 1 to Alen(this.aPattern, 1)
+ if !isdigit(left(tcString, 1)) and !isdigit(right(tcString, 1))
+ return tcString
+ endif
+* We try to identify a date format
+ local i
+ for i = 1 to alen(this.aPattern, 1)
_screen.oRegEx.pattern = this.aPattern[i, 1]
if _screen.oRegEx.Test(tcString)
- return this.formatDate(tcString, this.aPattern[i, 2])
+ return evl(this.formatDate(tcString, this.aPattern[i, 2]), tcString)
endif
- EndFor
- * It is a normal String
+ endfor
+* It is a normal String
return tcString
endfunc
- && ======================================================================== &&
- && Function FormatDate
- && return a valid date or datetime date type.
- && ======================================================================== &&
+&& ======================================================================== &&
+&& Function FormatDate
+&& return a valid date or datetime date type.
+&& ======================================================================== &&
function formatDate as variant
lparameters tcDate as string, tlUseDMY as Boolean
local lDate
lDate = .null.
- && IRODG 20210313 ISSUE # 14
+&& IRODG 20210313 ISSUE # 14
do case
case 'T' $ tcDate && JavaScript or ISO 8601 format.
do case
@@ -99,10 +131,17 @@ define class jsonutils as custom
finally
set date &setDateAct
endtry
- case occurs(':', tcDate) >= 2 && VFP Date Time Format. 'YYYY-mm-dd HH:mm:ss'
+ case occurs(':', tcDate) >= 2 && VFP Date Time Format. 'YYYY-mm-dd HH:mm:ss' and also 'dd-mm-yyyy hh:mm:ss'
try
setDateAct = set('Date')
- set date ymd
+* set date ymd
+&& (DCA) - 12/09/2023 - Also verify if the DateTime is DMY Format
+ if !tlUseDMY
+ set date ymd
+ else
+ set date dmy
+ endif
+
lDate = ctot(tcDate)
catch
lDate = {//::}
@@ -125,62 +164,70 @@ define class jsonutils as custom
endtry
endcase
return lDate
- && IRODG 20210313 ISSUE # 14
+&& IRODG 20210313 ISSUE # 14
endfunc
- && ======================================================================== &&
- && Function GetString
- && ======================================================================== &&
- function getstring as string
- lparameters tcString as string, tlParseUtf8 as Boolean
- tcString = allt(tcString)
- tcString = strtran(tcString, '\', '\\' )
+&& ======================================================================== &&
+&& Function GetString
+&& ======================================================================== &&
+ function getString as string
+ lparameters tcString as string, tlParseUTF8 as Boolean
+&& IRODG 08/08/2023 Inicio
+*tcString = Alltrim(tcString)
+&& IRODG 08/08/2023 Fin
+ tcString = strtran(tcString, '\', '\\' )
tcString = strtran(tcString, chr(9), '\t' )
tcString = strtran(tcString, chr(10), '\n' )
tcString = strtran(tcString, chr(13), '\r' )
+ if left(alltrim(tcString), 1) == '"' and right(alltrim(tcString),1) == '"'
+ tcString = substr(tcString, 2, len(tcString)-2)
+ endif
tcString = strtran(tcString, '"', '\"' )
- if tlParseUtf8
- tcString = StrTran(tcString,"&","\u0026")
- tcString = StrTran(tcString,"+","\u002b")
- tcString = StrTran(tcString,"-","\u002d")
- tcString = StrTran(tcString,"#","\u0023")
- tcString = StrTran(tcString,"%","\u0025")
- tcString = StrTran(tcString,"","\u00b2")
- tcString = StrTran(tcString,'','\u00e0')
- tcString = StrTran(tcString,'','\u00e1')
- tcString = StrTran(tcString,'','\u00e8')
- tcString = StrTran(tcString,'','\u00e9')
- tcString = StrTran(tcString,'','\u00ec')
- tcString = StrTran(tcString,'','\u00ed')
- tcString = StrTran(tcString,'','\u00f2')
- tcString = StrTran(tcString,'','\u00f3')
- tcString = StrTran(tcString,'','\u00f9')
- tcString = StrTran(tcString,'','\u00fa')
- tcString = StrTran(tcString,'','\u00fc')
- tcString = StrTran(tcString,'','\u00c0')
- tcString = StrTran(tcString,'','\u00c1')
- tcString = StrTran(tcString,'','\u00c8')
- tcString = StrTran(tcString,'','\u00c9')
- tcString = StrTran(tcString,'','\u00cc')
- tcString = StrTran(tcString,'','\u00cd')
- tcString = StrTran(tcString,'','\u00d2')
- tcString = StrTran(tcString,'','\u00d3')
- tcString = StrTran(tcString,'','\u00d9')
- tcString = StrTran(tcString,'','\u00da')
- tcString = StrTran(tcString,'','\u00dc')
- tcString = StrTran(tcString,'','\u00f1')
- tcString = StrTran(tcString,'','\u00d1')
- tcString = StrTran(tcString,'','\u00a9')
- tcString = StrTran(tcString,'','\u00ae')
- tcString = StrTran(tcString,'','\u00e7')
+ if tlParseUTF8
+ tcString = strtran(tcString,"&","\u0026")
+ tcString = strtran(tcString,"+","\u002b")
+ tcString = strtran(tcString,"-","\u002d")
+ tcString = strtran(tcString,"#","\u0023")
+ tcString = strtran(tcString,"%","\u0025")
+ tcString = strtran(tcString,"","\u00b2")
+ tcString = strtran(tcString,'','\u00e0')
+ tcString = strtran(tcString,'','\u00e1')
+ tcString = strtran(tcString,'','\u00e8')
+ tcString = strtran(tcString,'','\u00e9')
+ tcString = strtran(tcString,'','\u00ec')
+ tcString = strtran(tcString,'','\u00ed')
+ tcString = strtran(tcString,'','\u00f2')
+ tcString = strtran(tcString,'','\u00f3')
+ tcString = strtran(tcString,'','\u00f9')
+ tcString = strtran(tcString,'','\u00fa')
+ tcString = strtran(tcString,'','\u00fc')
+ tcString = strtran(tcString,'','\u00c0')
+ tcString = strtran(tcString,'','\u00c1')
+ tcString = strtran(tcString,'','\u00c8')
+ tcString = strtran(tcString,'','\u00c9')
+ tcString = strtran(tcString,'','\u00cc')
+ tcString = strtran(tcString,'','\u00cd')
+ tcString = strtran(tcString,'','\u00d2')
+ tcString = strtran(tcString,'','\u00d3')
+ tcString = strtran(tcString,'','\u00d9')
+ tcString = strtran(tcString,'','\u00da')
+ tcString = strtran(tcString,'','\u00dc')
+ tcString = strtran(tcString,'','\u00f1')
+ tcString = strtran(tcString,'','\u00d1')
+ tcString = strtran(tcString,'','\u00a9')
+ tcString = strtran(tcString,'','\u00ae')
+ tcString = strtran(tcString,'','\u00e7')
+ tcString = strtran(tcString,'','\u00ba')
endif
-
- return '"' +tcString + '"'
+ if left(alltrim(tcString), 1) != '"' and right(alltrim(tcString),1) != '"'
+ return '"'+tcString+'"'
+ endif
+ return tcString
endfunc
- && ======================================================================== &&
- && Function CheckProp
- && Check the object property name for invalid format (replace space with '_')
- && ======================================================================== &&
+&& ======================================================================== &&
+&& Function CheckProp
+&& Check the object property name for invalid format (replace space with '_')
+&& ======================================================================== &&
function checkprop(tcprop as string) as string
local lcfinalprop, i, lcchar
lcfinalprop = ''
@@ -193,48 +240,48 @@ define class jsonutils as custom
endif
endfor
return alltrim(lcfinalprop)
- EndFunc
-
- Function tokenTypeToStr(tnType)
+ endfunc
+
+ function tokenTypeToStr(tnType)
do case
case tnType = 0
- Return 'EOF'
+ return 'EOF'
case tnType = 1
- Return 'LBRACE'
+ return 'LBRACE'
case tnType = 2
- Return 'RBRACE'
+ return 'RBRACE'
case tnType = 3
- Return 'LBRACKET'
+ return 'LBRACKET'
case tnType = 4
- Return 'RBRACKET'
+ return 'RBRACKET'
case tnType = 5
- Return 'COMMA'
+ return 'COMMA'
case tnType = 6
- Return 'COLON'
+ return 'COLON'
case tnType = 7
- Return 'TRUE'
+ return 'TRUE'
case tnType = 8
- Return 'FALSE'
+ return 'FALSE'
case tnType = 9
- Return 'NULL'
+ return 'NULL'
case tnType = 10
- Return 'NUMBER'
+ return 'NUMBER'
case tnType = 11
- Return 'KEY'
+ return 'KEY'
case tnType = 12
- Return 'STRING'
+ return 'STRING'
case tnType = 13
- Return 'LINE'
+ return 'LINE'
case tnType = 14
- Return 'INTEGER'
+ return 'INTEGER'
case tnType = 15
- Return 'FLOAT'
+ return 'FLOAT'
case tnType = 16
- Return 'VALUE'
+ return 'VALUE'
case tnType = 17
- Return 'EOF'
+ return 'EOF'
case tnType = 18
return 'BOOLEAN'
- ENDCASE
- EndFunc
+ endcase
+ endfunc
enddefine
diff --git a/src/loader.prg b/src/loader.prg
index ff8e8f8..443b54b 100644
--- a/src/loader.prg
+++ b/src/loader.prg
@@ -4,10 +4,12 @@
Set Procedure To "src\JSONClass" Additive
Set Procedure To "src\Tokenizer" Additive
Set Procedure To "src\NetScanner" Additive
+Set Procedure To "src\jscriptscanner" Additive
Set Procedure To "src\Parser" Additive
Set Procedure To "src\JSONUtils" Additive
Set Procedure To "src\ArrayToCursor" Additive
Set Procedure To "src\CursorToArray" Additive
+Set Procedure To "src\CursorToJsonObject" Additive
Set Procedure To "src\JSONStringify" Additive
Set Procedure To "src\ObjectToJSON" Additive
Set Procedure To "src\JSONToRTF" Additive
@@ -41,6 +43,4 @@ Endif
If Type("_Screen.Toml") != "U"
=Removeproperty(_Screen, 'Toml')
Endif
-=AddProperty(_Screen, "Toml", Createobject("TomlClass"))
-
-Return
\ No newline at end of file
+=AddProperty(_Screen, "Toml", Createobject("TomlClass"))
\ No newline at end of file
diff --git a/src/objecttojson.prg b/src/objecttojson.prg
index 5db27cb..767418b 100644
--- a/src/objecttojson.prg
+++ b/src/objecttojson.prg
@@ -6,6 +6,9 @@ define class ObjectToJSON as session
cDateAct = ''
nOrden = 0
cFlags = ''
+ parseUTF8 = .f.
+ TrimChars = .f.
+
* Function Init
function init
this.lCentury = set("Century") == "OFF"
@@ -14,8 +17,9 @@ define class ObjectToJSON as session
set date ansi
mvcount = 60000
endfunc
+
* Encode
- function Encode(toRefObj, tcFlags)
+ function Encode(toRefObj, tcFlags, tlParseUTF8, tlTrimChars)
lPassByRef = .t.
try
external array toRefObj
@@ -23,12 +27,19 @@ define class ObjectToJSON as session
lPassByRef = .f.
endtry
this.cFlags = evl(tcFlags, ALL_MEMBERS)
+ this.parseUTF8 = tlParseUTF8
+ this.TrimChars = tlTrimChars
if lPassByRef
return this.AnyToJson(@toRefObj)
else
return this.AnyToJson(toRefObj)
endif
endfunc
+
+ function EncodeFromSchema(toRefObj, tcSchema, tlParseUTF8, tlTrimChars)
+
+ endfunc
+
* AnyToJson
function AnyToJson as memo
lparameters tValue as Variant
@@ -37,7 +48,6 @@ define class ObjectToJSON as session
catch
endtry
do case
- *case type("Alen(tValue, 1)") = "N"
case type("tValue", 1) = 'A'
local k, j, lcArray
if alen(tValue, 2) == 0
@@ -46,9 +56,9 @@ define class ObjectToJSON as session
for k = 1 to alen(tValue)
lcArray = lcArray + iif(len(lcArray) > 1, ',', '')
try
- *local array aLista(alen(tValue[k]))
- =acopy(tValue[k], aLista)
- lcArray = lcArray + this.AnyToJson(@aLista)
+ local array laLista[1]
+ =acopy(tValue[k], laLista)
+ lcArray = lcArray + this.AnyToJson(@laLista)
catch
lcArray = lcArray + this.AnyToJson(tValue[k])
endtry
@@ -67,8 +77,9 @@ define class ObjectToJSON as session
lcArray = lcArray + ','
endif
try
- =acopy(tValue[k, j], aLista)
- lcArray = lcArray + this.AnyToJson(@aLista)
+ local array laLista[1]
+ =acopy(tValue[k, j], laLista)
+ lcArray = lcArray + this.AnyToJson(@laLista)
catch
lcArray = lcArray + this.AnyToJson(tValue[k, j])
endtry
@@ -81,25 +92,63 @@ define class ObjectToJSON as session
return lcArray
case vartype(tValue) = 'O'
- local j, lcJSONStr, lnTot, i
+ local j, lcJSONStr, lnTot, i, lcProp, lcOriginalName
local array gaMembers(1)
lcJSONStr = '{'
lnTot = amembers(gaMembers, tValue, 0, this.cFlags)
+
+ local array laPropsToProcess[1]
+ local lnPropCount, lnIdx
+ lnPropCount = 0
+
+ * primer paso: identificar y clasificar las propiedades
for j=1 to lnTot
lcProp = lower(alltrim(gaMembers[j]))
- lcJSONStr = lcJSONStr + iif(len(lcJSONStr) > 1, ',', '') + '"' + lcProp + '":'
- try
- *local array aCopia(alen(tValue. &gaMembers[j]))
- =acopy(tValue. &gaMembers[j], aCopia)
- lcJSONStr = lcJSONStr + this.AnyToJson(@aCopia)
- catch
- try
- lcJSONStr = lcJSONStr + this.AnyToJson(tValue. &gaMembers[j])
- catch
- lcJSONStr = lcJSONStr + "{}"
- endtry
- endtry
+ * Ignoramos propiedades especiales de array
+ if left(lower(lcProp), 14) == "_specialarray_"
+ loop
+ endif
+
+ lnPropCount = lnPropCount + 1
+ dimension laPropsToProcess[lnPropCount,2]
+ laPropsToProcess[lnPropCount, 1] = lcProp
+ if right(lcProp, 6) == "_array" and type("tValue._specialArray_" + lcProp) == "C"
+ laPropsToProcess[lnPropCount, 2] = "special_array"
+ else
+ laPropsToProcess[lnPropCount, 2] = "normal"
+ endif
+ next
+
+ * segundo paso: procesar las propiedades filtradas
+ for lnIdx=1 to lnPropCount
+ lcProp = laPropsToProcess[lnIdx, 1]
+
+ if laPropsToProcess[lnIdx, 2] == "special_array"
+ lcOriginalName = evaluate("tValue._specialArray_" + lcProp)
+ lcJSONStr = lcJSONStr + iif(len(lcJSONStr) > 1, ',', '') + '"' + lcOriginalName + '":'
+ try
+ local array laLista[1]
+ =acopy(tValue. &lcProp, laLista)
+ lcJSONStr = lcJSONStr + this.AnyToJson(@laLista)
+ catch
+ lcJSONStr = lcJSONStr + "[]"
+ endtry
+ else
+ * Es una propiedad normal
+ lcJSONStr = lcJSONStr + iif(len(lcJSONStr) > 1, ',', '') + '"' + lcProp + '":'
+ try
+ local array laLista[1]
+ =acopy(tValue. &lcProp, laLista)
+ lcJSONStr = lcJSONStr + this.AnyToJson(@laLista)
+ catch
+ try
+ lcJSONStr = lcJSONStr + this.AnyToJson(tValue. &lcProp)
+ catch
+ lcJSONStr = lcJSONStr + "{}"
+ endtry
+ endtry
+ endif
endfor
*//> Collection based class object support
@@ -121,7 +170,7 @@ define class ObjectToJSON as session
lcJSONStr = lcJSONStr + '}'
return lcJSONStr
otherwise
- return _screen.JSONUtils.GetValue(tValue, vartype(tValue))
+ return _screen.JSONUtils.GetValue(tValue, vartype(tValue), this.parseUTF8, this.TrimChars)
endcase
endfunc
* Destroy
diff --git a/src/parser.prg b/src/parser.prg
index f03e640..363b30d 100644
--- a/src/parser.prg
+++ b/src/parser.prg
@@ -8,24 +8,27 @@
&& array = '[' value | { ',' value } ']'
&& ======================================================================== &&
define class Parser as custom
- Dimension tokens[1]
Hidden current
Hidden previous
Hidden peek
+ hidden problematicFields
+ hidden tokenCollection
function init(toScanner)
- With this
- Local laTokens
- laTokens = toScanner.scanTokens()
- =Acopy(laTokens, .tokens)
- .current = 1
- endwith
+ this.tokenCollection = toScanner.scanTokens()
+ this.current = 1
+
+ this.problematicFields = createobject("Collection")
+ this.problematicFields.Add("messages", "messages")
+ this.problematicFields.Add("update", "update")
endfunc
- function Parse
- With this
- Return .value()
- endwith
+ function Parse
+ local loParsedObject
+ loParsedObject = this.value()
+ this.CleanUp()
+
+ return loParsedObject
endfunc
&& ======================================================================== &&
&& Function Object
@@ -33,62 +36,65 @@ define class Parser as custom
&& kvp = KEY ':' value
&& ======================================================================== &&
hidden function object as object
- With this
- local loObj, loPair, lcMacro
- loObj = createobject('Empty')
+ local loObj, loPair, lcMacro
+ loObj = createobject('Empty')
+ if !this.check(T_RBRACE)
+ loPair = this.kvp()
+ this.addKeyValuePair(@loObj, @loPair)
- if !.check(T_RBRACE)
- loPair = .kvp()
- .addKeyValuePair(@loObj, @loPair)
-
- do while .match(T_COMMA)
- loPair = .kvp()
- .addKeyValuePair(@loObj, @loPair)
- enddo
- endif
- .consume(T_RBRACE, "Expect '}' after JSON body.")
-
- return loObj
- endwith
+ do while this.match(T_COMMA)
+ loPair = this.kvp()
+ this.addKeyValuePair(@loObj, @loPair)
+ enddo
+ endif
+ this.consume(T_RBRACE, "Expect '}' after JSON body.")
+ return loObj
endfunc
&& ======================================================================== &&
&& Function Kvp
&& EBNF -> kvp = KEY ':' value
&& ======================================================================== &&
hidden function kvp(toObj)
- With this
- local loPair, lvValue
-
- loPair = CreateObject('Empty')
- =AddProperty(loPair, 'key', '')
-
- .consume(T_STRING, "Expect key name")
-
- loPair.key = _screen.jsonUtils.CheckProp(.previous.value)
-
- .consume(T_COLON, "Expect ':' after key element.")
-
- lvValue = .value()
- If Type('lvValue', 1) != 'A'
- =AddProperty(loPair, 'value', lvValue)
- Else
- =AddProperty(loPair, 'value[1]', .Null.)
- Acopy(lvValue, loPair.value)
- endif
-
- Return loPair
- EndWith
+ local loPair, lvValue
+
+ loPair = CreateObject('Empty')
+ =AddProperty(loPair, 'key', '')
+
+ this.consume(T_STRING, "Expect key name")
+ loPair.key = _screen.jsonUtils.CheckProp(this.previous.value)
+ this.consume(T_COLON, "Expect ':' after key element.")
+ lvValue = this.value()
+ If Type('lvValue', 1) != 'A'
+ =AddProperty(loPair, 'value', lvValue)
+ Else
+ =AddProperty(loPair, 'value[1]', .Null.)
+ Acopy(lvValue, loPair.value)
+ endif
+ Return loPair
EndFunc
Hidden function addKeyValuePair(toObject, toPair)
If Type('toPair.value', 1) != 'A'
=AddProperty(toObject, toPair.key, toPair.value)
- Else
- Local lcMacro
- lcMacro = "AddProperty(toObject, '" + toPair.key + "[1]', .Null.)"
- &lcMacro
- lcMacro = "Acopy(toPair.value, toObject." + toPair.key + ")"
- &lcMacro
+ else
+ local lcMacro
+ if this.problematicFields.GetKey(toPair.key) > 0
+ local lcArrayName
+ lcArrayName = toPair.key + "_array"
+ lcMacro = "AddProperty(toObject, '" + lcArrayName + "[1]', .null.)"
+ &lcMacro
+
+ lcMacro = "Acopy(toPair.value, toObject." + lcArrayName + ")"
+ &lcMacro
+
+ =addproperty(toObject, "_specialArray_" + lcArrayName, toPair.key)
+ else
+ Local lcMacro
+ lcMacro = "AddProperty(toObject, '" + toPair.key + "[1]', .Null.)"
+ &lcMacro
+ lcMacro = "Acopy(toPair.value, toObject." + toPair.key + ")"
+ &lcMacro
+ endif
EndIf
EndFunc
@@ -97,114 +103,104 @@ define class Parser as custom
&& EBNF -> value = STRING | NUMBER | BOOLEAN | array | object | NULL
&& ======================================================================== &&
hidden function value
- With this
- do case
- case .match(T_STRING)
- return _screen.jsonUtils.CheckString(.previous.value)
-
- case .match(T_NUMBER)
- Local lcValue, lcPoint
- lcValue = .previous.value
- lcPoint = Set("Point")
-
- If lcPoint != '.'
- lcValue = Strtran(lcValue, '.', lcPoint)
- EndIf
- return iif(at(lcPoint, lcValue) > 0, Val(lcValue), int(Val(lcValue)))
-
- case .match(T_BOOLEAN)
- return (.previous.value == 'true')
+ do case
+ case this.match(T_STRING)
+ return _screen.jsonUtils.CheckString(this.previous.value)
+
+ case this.match(T_NUMBER)
+ Local lcValue, lcPoint
+ lcValue = this.previous.value
+ lcPoint = Set("Point")
+
+ If lcPoint != '.'
+ lcValue = Strtran(lcValue, '.', lcPoint)
+ EndIf
+ return iif(at(lcPoint, lcValue) > 0, Val(lcValue), int(Val(lcValue)))
+
+ case this.match(T_BOOLEAN)
+ return (this.previous.value == 'true')
- case .match(T_LBRACE)
- return .object()
+ case this.match(T_LBRACE)
+ return this.object()
- case .match(T_LBRACKET)
- return @.array()
-
- case .match(T_NULL)
- return .null.
- otherwise
- error "Parser Error: Unknown token value: '" + _screen.jsonUtils.tokenTypeToStr(.peek.type) + "'"
- EndCase
- EndWith
+ case this.match(T_LBRACKET)
+ return @this.array()
+
+ case this.match(T_NULL)
+ return .null.
+ otherwise
+ error "Parser Error: Unknown token value: '" + _screen.jsonUtils.tokenTypeToStr(this.peek.type) + "'"
+ EndCase
endfunc
&& ======================================================================== &&
&& Function Array
&& EBNF -> array = '[' value | { ',' value } ']'
&& ======================================================================== &&
hidden function array
- With this
- local laArray
- laArray = createobject("TParserInternalArray")
- If !.check(T_RBRACKET)
- laArray.Push(.value())
- do while .match(T_COMMA)
- laArray.Push(.value())
- enddo
- endif
- .consume(T_RBRACKET, "Expect ']' after array elements.")
-
- return @laArray.getArray()
- endwith
+ local laArray
+ laArray = createobject("TParserInternalArray")
+ If !this.check(T_RBRACKET)
+ laArray.Push(this.value())
+ do while this.match(T_COMMA)
+ laArray.Push(this.value())
+ enddo
+ endif
+ this.consume(T_RBRACKET, "Expect ']' after array elements.")
+ return @laArray.getArray()
endfunc
Function match(tnTokenType)
- With this
- If .check(tnTokenType)
- .advance()
- Return .t.
- EndIf
- Return .f.
- endwith
+ If this.check(tnTokenType)
+ this.advance()
+ Return .t.
+ EndIf
+ Return .f.
EndFunc
Hidden Function consume(tnTokenType, tcMessage)
- With this
- If .check(tnTokenType)
- Return .advance()
- EndIf
- if empty(tcMessage)
- tcMessage = "Parser Error: expected token '" + _screen.jsonUtils.tokenTypeToStr(tnTokenType) + "' got = '" + _screen.jsonUtils.tokenTypeToStr(.peek.type) + "'"
- endif
- error tcMessage
- EndWith
+ If this.check(tnTokenType)
+ Return this.advance()
+ EndIf
+ if empty(tcMessage)
+ tcMessage = "Parser Error: expected token '" + _screen.jsonUtils.tokenTypeToStr(tnTokenType) + "' got = '" + _screen.jsonUtils.tokenTypeToStr(this.peek.type) + "'"
+ endif
+ error tcMessage
endfunc
Hidden Function check(tnTokenType)
- With this
- If .isAtEnd()
- Return .f.
- EndIf
- Return .peek.type == tnTokenType
- endwith
+ If this.isAtEnd()
+ Return .f.
+ EndIf
+ Return this.peek.type == tnTokenType
EndFunc
Hidden Function advance
- With this
- If !.isAtEnd()
- .current = .current + 1
- EndIf
- Return .tokens[.current-1]
- endwith
+ If !this.isAtEnd()
+ this.current = this.current + 1
+ EndIf
+ Return this.tokenCollection.tokens[this.current-1]
endfunc
Hidden Function isAtEnd
- With this.peek
- Return .type == T_EOF
- endwith
+ Return this.peek.type == T_EOF
endfunc
Hidden Function peek_access
- With this
- Return .tokens[.current]
- endwith
+ Return this.tokenCollection.tokens[this.current]
endfunc
Hidden Function previous_access
- With this
- Return .tokens[.current-1]
+ Return this.tokenCollection.tokens[this.current-1]
+ endfunc
+
+ function CleanUp
+ with this
+ .TokenCollection = .null.
+ .current = 0
+ .previous = .null.
+ .peek = .null.
endwith
- EndFunc
+ endfunc
EndDefine
@@ -216,16 +212,12 @@ Define Class TParserInternalArray As Custom
nIndex = 0
Function Push(tvItem)
- With this
- .nIndex = .nIndex + 1
- Dimension .aCustomArray[.nIndex]
- .aCustomArray[.nIndex] = tvItem
- EndWith
+ this.nIndex = this.nIndex + 1
+ Dimension this.aCustomArray[this.nIndex]
+ this.aCustomArray[this.nIndex] = tvItem
Endfunc
Function GetArray
- With this
- Return @.aCustomArray
- endwith
+ Return @this.aCustomArray
EndFunc
Enddefine
\ No newline at end of file
diff --git a/src/testScanner.prg b/src/testScanner.prg
new file mode 100644
index 0000000..a17e9d1
--- /dev/null
+++ b/src/testScanner.prg
@@ -0,0 +1,23 @@
+clear
+cd f:\desarrollo\github\jsonfox\src\
+delete file "F:\Desarrollo\GitHub\JSONFox\trace.log"
+Do loader
+
+lbUserJScriptTokenizer = .F.
+lcClass = "JScriptScanner"
+set procedure to "JScriptScanner" additive
+
+local lnStart
+lnStart = seconds()
+local loScanner
+*lcFile = "F:\Desarrollo\GitHub\JSONFox\test.json"
+lcFile = "c:\a1\registro-gastos\node_modules\.cache\babel-loader\38c18daa8cb0956e273a42f3cabeb7a3ca3c829d7c9614a3fabaecc14d68db02.json"
+loScanner = createobject(lcClass, strconv(filetostr(lcFile),11))
+loResult = loScanner.ScanTokens()
+? seconds() - lnStart
+return
+
+for each loToken in loResult
+ lcOutput = loScanner.TokenStr(loToken)
+ loScanner.log(lcOutput)
+endfor
\ No newline at end of file
diff --git a/src/tokenizer.prg b/src/tokenizer.prg
index 17b0e8b..ec8680a 100644
--- a/src/tokenizer.prg
+++ b/src/tokenizer.prg
@@ -1,105 +1,93 @@
-* <>
-* =========================================
-*!* Clear
-*!* Cd f:\desarrollo\github\jsonfox\src\
-*!* sc = CreateObject("Tokenizer", '"string"')
-*!* tokens = sc.scanTokens()
-*!* For i = 1 to Alen(tokens)
-*!* ? sc.tokenStr(tokens[i])
-*!* Endfor
-* =========================================
-* <>
-
#include "JSONFox.h"
* Tokenizer
define class Tokenizer as custom
- Hidden source
- Hidden start
- Hidden current
- Hidden letters
- Hidden hexLetters
+ hidden source
+ hidden start
+ hidden current
+ hidden letters
+ hidden hexLetters
hidden line
-
- Hidden capacity
- Hidden length
-
- Dimension tokens[1]
+
+ hidden capacity
+ hidden length
+
+ dimension tokens[1]
sourceLen = 0
-
-
+
function init(tcSource)
- With this
+ with this
.length = 1
.capacity = 0
+&& IRODG 11/08/2023 Inicio
+* We remove possible invalid characters from the input source.
+ tcSource = strtran(tcSource, chr(0))
+ tcSource = strtran(tcSource, chr(10))
+ tcSource = strtran(tcSource, chr(13))
+&& IRODG 11/08/2023 Fin
.source = tcSource
.start = 0
.current = 1
.letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
- .hexLetters = 'abcdefABCDEF'
+ .hexLetters = 'abcdefABCDEF'
.line = 1
- .sourceLen = Len(tcSource)
+ .sourceLen = len(tcSource)
endwith
endfunc
- Hidden function advance
- With this
+ hidden function advance
+ with this
.current = .current + 1
- Return substr(.source, .current-1, 1)
+ return substr(.source, .current-1, 1)
endwith
endfunc
- Hidden function peek
- With this
- If .isAtEnd()
- Return ''
- EndIf
+ hidden function peek
+ with this
+ if .isAtEnd()
+ return ''
+ endif
return substr(.source, .current, 1)
endwith
- EndFunc
-
- Hidden function peekNext
- With this
- If (.current + 1) > .sourceLen
- Return ''
- EndIf
+ endfunc
+
+ hidden function peekNext
+ with this
+ if (.current + 1) > .sourceLen
+ return ''
+ endif
return substr(.source, .current+1, 1)
endwith
- endfunc
-
- Hidden Function skipWhitespace
- With this
- LOCAL ch
- Do while InList(.peek(), Chr(9), Chr(10), Chr(13), Chr(32))
+ endfunc
+
+ hidden function skipWhitespace
+ with this
+ local ch
+ do while inlist(.peek(), chr(9), chr(10), chr(13), chr(32))
ch = .advance()
- If ch == Chr(10)
+ if ch == chr(10)
.line = .line + 1
endif
- EndDo
+ enddo
endwith
endfunc
- Hidden function identifier
- With this
- Local lexeme
- do while At(.peek(), .letters) > 0
+ hidden function identifier
+ with this
+ local lexeme
+ do while at(.peek(), .letters) > 0
.advance()
- EndDo
- lexeme = Substr(.source, .start, .current-.start)
-*!* If .current == Len(.source)
-*!* lexeme = Substr(.source, .start, (.current+1)-.start)
-*!* Else
-*!* lexeme = Substr(.source, .start, .current-.start)
-*!* endif
+ enddo
+ lexeme = substr(.source, .start, .current-.start)
if inlist(lexeme, "true", "false", "null")
return .addToken(iif(lexeme == 'null', T_NULL, T_BOOLEAN), lexeme)
else
.showError(.line, "Lexer Error: Unexpected identifier '" + lexeme + "'")
- EndIf
- EndWith
+ endif
+ endwith
endfunc
- Hidden function number
- With this
+ hidden function number
+ with this
local lexeme, isNegative
lexeme = ''
isNegative = (.peek() == '-')
@@ -116,39 +104,50 @@ define class Tokenizer as custom
do while isdigit(.peek())
.advance()
enddo
- EndIf
- lexeme = Substr(.source, .start, .current-.start)
+ endif
+
+&& Check if number is a Scientific Notation in Tokenizer.Number()
+ if lower(.peek() + .peekNext()) == "e+"
+ .advance()
+ .advance()
+ do while isdigit(.peek())
+ .advance()
+ enddo
+ endif
+*****************************************************************
+
+ lexeme = substr(.source, .start, .current-.start)
return .addToken(T_NUMBER, lexeme)
endwith
endfunc
- Hidden function string
- With this
- Local lexeme, ch
- do while !.isAtEnd()
+ hidden function string
+ with this
+ local lexeme, ch
+ do while !.isAtEnd()
ch = .peek()
- Do case
- case ch == '\' and InList(.peekNext(), '\', '/', 'n', 'r', 't', '"', "'")
+ do case
+ case ch == '\' and inlist(.peekNext(), '\', '/', 'n', 'r', 't', '"', "'")
.advance()
- Case ch = '"'
+ case ch = '"'
.advance()
- Exit
- Case ch == ',' and InList(.peekNext(), '"', "'") and Type('This._anyType_') == 'C' and Alltrim(this._anyType_) == 'anyType'
+ exit
+ case ch == ',' and inlist(.peekNext(), '"', "'") and type('This._anyType_') == 'C' and alltrim(this._anyType_) == 'anyType'
.advance()
- Exit
+ exit
endcase
.advance()
- EndDo
-
- lexeme = Substr(.source, .start+1, .current-.start-2)
+ enddo
+
+ lexeme = substr(.source, .start+1, .current-.start-2)
.escapeCharacters(@lexeme)
- .checkUnicodeFormat(@lexeme)
+ .checkUnicodeFormat(@lexeme)
return .addToken(T_STRING, lexeme)
endwith
- EndFunc
+ endfunc
- Hidden function currency
- With this
+ hidden function currency
+ with this
local lexeme, isNegative
lexeme = ''
isNegative = (.peek() == '-')
@@ -158,161 +157,251 @@ define class Tokenizer as custom
do while isdigit(.peek())
.advance()
- EndDo
+ enddo
- * Loop while there is a comma ','
- Do while .t.
- If .peek() == ',' and IsDigit(.peekNext())
+* Loop while there is a comma ','
+ do while .t.
+ if .peek() == ',' and isdigit(.peekNext())
.advance() && eat the comma ','
do while isdigit(.peek())
.advance()
- EndDo
- Else
+ enddo
+ else
exit
- EndIf
- enddo
+ endif
+ enddo
- * Check for decimal part
+* Check for decimal part
if .peek() == '.' and isdigit(.peekNext())
.advance() && eat the dot '.'
do while isdigit(.peek())
.advance()
enddo
- EndIf
- lexeme = Substr(.source, .start+1, .current-.start)
- return .addToken(T_NUMBER, Strtran(lexeme, ','))
+ endif
+ lexeme = substr(.source, .start+1, .current-.start)
+ return .addToken(T_NUMBER, strtran(lexeme, ','))
endwith
endfunc
+ procedure escapeCharacters(tcLexeme)
+ if len(tcLexeme) < 100
+ local lcResult, i, lcChar, lcNextChar
+ lcResult = ""
+ i = 1
- Procedure escapeCharacters(tcLexeme)
- * Convert all escape sequences
- tcLexeme = Strtran(tcLexeme, '\\', '\')
- tcLexeme = Strtran(tcLexeme, '\/', '/')
- tcLexeme = Strtran(tcLexeme, '\n', Chr(10))
- tcLexeme = Strtran(tcLexeme, '\r', Chr(13))
- tcLexeme = Strtran(tcLexeme, '\t', Chr(9))
- tcLexeme = Strtran(tcLexeme, '\"', '"')
- tcLexeme = Strtran(tcLexeme, "\'", "'")
- EndProc
-
- procedure checkUnicodeFormat(tcLexeme)
- * Look for unicode format
- _Screen.oRegEx.Pattern = "\\u([a-fA-F0-9]{4})"
- Local loResult, lcValue, i
- _Screen.oRegEx.IgnoreCase = .t.
- _Screen.oRegEx.global = .t.
- loResult = _Screen.oRegEx.Execute(tcLexeme)
- If Type('loResult') == 'O'
- For i = 0 to loResult.Count-1
- lcValue = loResult.Item[i].Value
- Try
- tcLexeme = Strtran(tcLexeme, lcValue, Strconv(lcValue, 16))
- Catch
- EndTry
- EndFor
- EndIf
- EndProc
-
- Function scanTokens
- With this
- Dimension .tokens[1]
- Do while !.isAtEnd()
+ do while i <= len(tcLexeme)
+ lcChar = substr(tcLexeme, i, 1)
+ if lcChar == "\" and i < len(tcLexeme)
+ lcNextChar = substr(tcLexeme, i + 1, 1)
+ do case
+ case lcNextChar == "\"
+ lcResult = lcResult + "\"
+ case lcNextChar == "/"
+ lcResult = lcResult + "/"
+ case lcNextChar == "n"
+ lcResult = lcResult + chr(10)
+ case lcNextChar == "r"
+ lcResult = lcResult + chr(13)
+ case lcNextChar == "t"
+ lcResult = lcResult + chr(9)
+ case lcNextChar == '"'
+ lcResult = lcResult + '"'
+ case lcNextChar == "'"
+ lcResult = lcResult + "'"
+ otherwise
+* Si no es una secuencia de escape conocida, mantener ambos caracteres
+ lcResult = lcResult + "\" + lcNextChar
+ endcase
+ i = i + 2 && Avanzar 2 caracteres
+ else
+ lcResult = lcResult + lcChar
+ i = i + 1 && avanzar un carcter
+ endif
+ enddo
+ tcLexeme = lcResult
+ else
+ tcLexeme = strtran(tcLexeme, '\\', '\')
+ tcLexeme = strtran(tcLexeme, '\/', '/')
+ tcLexeme = strtran(tcLexeme, '\n', chr(10))
+ tcLexeme = strtran(tcLexeme, '\r', chr(13))
+ tcLexeme = strtran(tcLexeme, '\t', chr(9))
+ tcLexeme = strtran(tcLexeme, '\"', '"')
+ tcLexeme = strtran(tcLexeme, "\'", "'")
+ endif
+ endproc
+
+ procedure checkUnicodeFormat(tcLexeme)
+* Look for unicode format
+** This conversion is better (in performance) than Regular Expressions.
+&& IRODG 09/10/2023 Inicio
+ local lcUnicode, lcConversion, lbReplace, lnPos
+ lnPos = 1
+ do while .t.
+ lbReplace = .f.
+ lcUnicode = substr(tcLexeme, at('\u', tcLexeme, lnPos), 6)
+ if len(lcUnicode) == 6
+ lbReplace = .t.
+ else
+ lcUnicode = substr(tcLexeme, at('\U', tcLexeme, lnPos), 6)
+ if len(lcUnicode) == 6
+ lbReplace = .t.
+ endif
+ endif
+ if lbReplace
+ tcLexeme = strtran(tcLexeme, lcUnicode, strtran(strconv(lcUnicode,16), chr(0)))
+ else
+ exit
+ endif
+ enddo
+&& IRODG 09/10/2023 Fin
+ endproc
+
+ function scanTokens
+ with this
+ dimension .tokens[1]
+ do while !.isAtEnd()
.skipWhitespace()
.start = .current
.scanToken()
- EndDo
+ enddo
.addToken(T_EOF, "")
.capacity = .length-1
-
- * Shrink array
- Dimension .tokens[.capacity]
-
- Return @.tokens
- EndWith
+
+* Shrink array
+ dimension .tokens[.capacity]
+
+ local loTokens
+ loTokens = createobject("Empty")
+ addproperty(loTokens, "tokens["+alltrim(str(.capacity))+"]", null)
+
+* Crear una copia de los tokens
+ local i
+ for i = 1 to .capacity
+* Si los tokens son objetos, crear copias profundas
+ if type('.tokens[i]') = 'O'
+ loTokens.tokens[i] = createobject("Empty")
+ =addproperty(loTokens.tokens[i], "type", .tokens[i].type)
+ =addproperty(loTokens.tokens[i], "value", .tokens[i].value)
+ =addproperty(loTokens.tokens[i], "line", .tokens[i].line)
+ else
+ loTokens.tokens[i] = .tokens[i]
+ endif
+ next
+
+ .CleanUp()
+
+ return loTokens
+ endwith
endfunc
- Hidden function scanToken
- With this
- Local ch
- ch = .advance()
- Do case
+ hidden function scanToken
+ with this
+ local ch
+ ch = .advance()
+ do case
case ch == '{'
- Return .addToken(T_LBRACE, ch)
+ return .addToken(T_LBRACE, ch)
case ch == '}'
- Return .addToken(T_RBRACE, ch)
-
+ return .addToken(T_RBRACE, ch)
+
case ch == '['
- Return .addToken(T_LBRACKET, ch)
+ return .addToken(T_LBRACKET, ch)
case ch == ']'
- Return .addToken(T_RBRACKET, ch)
+ return .addToken(T_RBRACKET, ch)
case ch == ':'
- Return .addToken(T_COLON, ch)
+ return .addToken(T_COLON, ch)
case ch == ','
- Return .addToken(T_COMMA, ch)
+ return .addToken(T_COMMA, ch)
- Case ch == '"'
- Return .string()
- Case ch == '$'
- Return .Currency()
- Otherwise
+ case ch == '"'
+ return .string()
+ case ch == '$'
+ return .currency()
+ otherwise
if isdigit(ch) or (ch == '-' and isdigit(.peek()))
- Return .number()
+ return .number()
endif
- if At(ch, .letters) > 0
- Return .identifier()
+ if at(ch, .letters) > 0
+ return .identifier()
endif
- .showError(.line, "Unknown character ['" + transform(ch) + "'], ascii: [" + TRANSFORM(ASC(ch)) + "]")
- EndCase
- EndWith
- EndFunc
+ .showError(.line, "Unknown character ['" + transform(ch) + "'], ascii: [" + transform(asc(ch)) + "]")
+ endcase
+ endwith
+ endfunc
hidden function addToken(tnTokenType, tcTokenValue)
- With this
+ with this
.checkCapacity()
+
local loToken
loToken = createobject("Empty")
=addproperty(loToken, "type", tnTokenType)
=addproperty(loToken, "value", tcTokenValue)
- =AddProperty(loToken, "line", .line)
-
+ =addproperty(loToken, "line", .line)
+
.tokens[.length] = loToken
.length = .length + 1
- EndWith
- EndFunc
-
- Hidden function checkCapacity
- With this
- If .capacity < .length + 1
- If Empty(.capacity)
+ endwith
+ endfunc
+
+ hidden function checkCapacity
+ with this
+ if .capacity < .length + 1
+ if empty(.capacity)
.capacity = 8
- Else
+ else
.capacity = .capacity * 2
- EndIf
- Dimension .tokens[.capacity]
- EndIf
+ endif
+ dimension .tokens[.capacity]
+ endif
endwith
endfunc
function showError(tnLine, tcMessage)
- error "SYNTAX ERROR: (" + TRANSFORM(tnLine) + ":" + TRANSFORM(this.current) + ")" + tcMessage
+ error "SYNTAX ERROR: (" + transform(tnLine) + ":" + transform(this.current) + ")" + tcMessage
endfunc
function isAtEnd
- With this
- return .current > .sourceLen
- EndWith
+ with this
+ return .current > .sourceLen
+ endwith
endfunc
function tokenStr(toToken)
local lcType, lcValue
lcType = _screen.jsonUtils.tokenTypeToStr(toToken.type)
- lcValue = alltrim(transform(toToken.value))
- return "Token(" + lcType + ", '" + lcValue + "') at Line(" + Alltrim(Str(toToken.Line)) + ")"
- EndFunc
-enddefine
\ No newline at end of file
+ lcValue = alltrim(transform(toToken.value))
+ return "Token(" + lcType + ", '" + lcValue + "') at Line(" + alltrim(str(toToken.line)) + ")"
+ endfunc
+
+ function CleanUp
+ with this
+* Liberar el array de tokens
+ if type('this.tokens', 1) == 'A' and alen(this.tokens) > 1
+ local i
+ for i = 1 to alen(this.tokens)
+ if type('this.tokens[i]') = 'O'
+* Liberar propiedades del objeto token
+ this.tokens[i] = .null.
+ endif
+ next
+* Redimensionar el array a tamao mnimo
+ dimension this.tokens[1]
+ this.tokens[1] = .null.
+ endif
+
+* Liberar otras variables que puedan ocupar mucha memoria
+ this.source = ""
+ this.sourceLen = 0
+ this.capacity = 0
+ this.length = 1
+ endwith
+ return .t.
+ endfunc
+
+enddefine