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..e2cdb54 100644
Binary files a/JSONFox.PJT and b/JSONFox.PJT differ
diff --git a/JSONFox.pjx b/JSONFox.pjx
index d3af685..728ac59 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..c845ccc 100644
Binary files a/jsonfox.app and b/jsonfox.app differ
diff --git a/src/arraytocursor.prg b/src/arraytocursor.prg
index 1b89124..f4721bd 100644
--- a/src/arraytocursor.prg
+++ b/src/arraytocursor.prg
@@ -114,7 +114,7 @@ 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
@@ -125,9 +125,16 @@ Define Class ArrayToCursor As Session
lxValue = .Value()
lcType = Vartype(lxValue)
lnFieldLength = 0
+ lnDecimals = 0
Do Case
Case lcType == 'N'
- lcType = Iif(Occurs('.', Transform(lxValue)) > 0, 'N', 'I')
+ lcType = Iif(Occurs('.', Transform(lxValue)) > 0 or lxValue > INTEGER_MAX_CAPACITY, 'N', 'I')
+ && >>>>>>> IRODG 03/17/24
+ If lcType == 'N'
+ lnFieldLength = Len(Transform(lxValue))
+ lnDecimals = len(GetWordNum(Transform(lxValue),2,'.'))
+ EndIf
+ && <<<<<<< IRODG 03/17/24
Case lcType == 'C'
If Len(lxValue) > STRING_MAX_SIZE
lcType = 'M'
@@ -140,7 +147,7 @@ Define Class ArrayToCursor As Session
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")
@@ -218,8 +225,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 +242,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 +263,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
diff --git a/src/cursortoarray.prg b/src/cursortoarray.prg
index 3b414ea..327db3f 100644
--- a/src/cursortoarray.prg
+++ b/src/cursortoarray.prg
@@ -2,6 +2,12 @@
define class CursorToArray as session
nSessionID = 0
CurName = ""
+ && IRODG 07/10/2023 Inicio
+ ParseUTF8 = .f.
+ && IRODG 07/10/2023 Fin
+ && IRODG 27/10/2023 Inicio
+ TrimChars = .F.
+ && IRODG 27/10/2023 Fin
* Function CursorToArray
function CursorToArray as memo
if !empty(this.nSessionID)
@@ -9,7 +15,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 +53,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,10 +70,25 @@ define class CursorToArray as session
else
lcValue = 'null'
endif
- otherwise
- lcValue = JSONUtils.GetString(alltrim(lcValue))
+ Otherwise
+ && IRODG 08/08/2023 Inicio
+ *lcValue = JSONUtils.GetString(alltrim(lcValue))
+ && IRODG 07/10/2023 Inicio
+*!* lcValue = JSONUtils.GetString(lcValue)
+ && IRODG 27/10/2023 Inicio
+ * lcValue = JSONUtils.GetString(lcValue, this.ParseUTF8)
+ lcValue = JSONUtils.GetString(Iif(this.TrimChars, Alltrim(lcValue), lcValue), this.ParseUTF8)
+ && IRODG 27/10/2023 Fin
+ && IRODG 07/10/2023 Fin
+ && IRODG 08/08/2023 Fin
endcase
- lcOutput = lcOutput + alltrim(lcValue)
+ && IRODG 08/08/2023 Inicio
+ *lcOutput = lcOutput + alltrim(lcValue)
+ && IRODG 27/10/2023 Inicio
+*!* lcOutput = lcOutput + lcValue
+ lcOutput = lcOutput + Iif(this.TrimChars, Alltrim(lcValue), lcValue)
+ && IRODG 27/10/2023 Fin
+ && IRODG 08/08/2023 Fin
case aColumns[i, 2] $ "YFIN"
lcOutput = lcOutput + transform(lcValue)
case aColumns[i, 2] = "L"
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.scx b/src/frmjsonviewer.scx
index 0b3951e..0bdc70b 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..cbc08ff 100644
--- a/src/jsonclass.prg
+++ b/src/jsonclass.prg
@@ -4,7 +4,7 @@ define class JSONClass as session
LastErrorText = ""
lError = .f.
lShowErrors = .t.
- version = "9.7"
+ version = "10.6"
hidden lInternal
hidden lTablePrompt
Dimension aCustomArray[1]
@@ -12,6 +12,10 @@ define class JSONClass as session
* Set this property to .T. if you want the lexer uses JSONFoxHelper.dll
NETScanner = .f.
&& <<<<<<< IRODG 07/01/21
+
+ && >>>>>>> IRODG 02/27/24
+ JScriptScanner = .f.
+ && <<<<<<< IRODG 02/27/24
*Function Init
function init
@@ -32,11 +36,14 @@ define class JSONClass as session
try
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()
@@ -56,11 +63,40 @@ define class JSONClass as session
Else
return loJSONObj
EndIf
- endfunc
+ ENDFUNC
+
+ * tokenize
+ FUNCTION dumpTokens
+ lparameters tcJsonStr as memo, tcOutput as string
+ try
+ this.ResetError()
+ local lexer
+ do case
+ case this.NETScanner
+ lexer = createobject("NetScanner", tcJsonStr)
+ case this.JScriptScanner
+ lexer = createobject("JScriptScanner", tcJsonStr)
+ otherwise
+ lexer = createobject("Tokenizer", tcJsonStr)
+ endcase
+ Local laTokens
+ laTokens = lexer.scanTokens()
+ IF FILE(tcOutput)
+ DELETE FILE (tcOutput)
+ ENDIF
+ FOR EACH loToken IN laTokens
+ STRTOFILE(tokenStr(loToken), tcOutput, 1)
+ ENDFOR
+ catch to loEx
+ this.ShowExceptionError(loEx)
+ finally
+ release lexer, laTokens
+ 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
lcTypeFlag = type('tcFlags')
@@ -79,12 +115,11 @@ define class JSONClass as session
release objToJson
endtry
endif
-
try
local lexer, parser
lexer = createobject("Tokenizer", tvNewVal)
parser = createobject("JSONStringify", lexer)
- loJSONStr = parser.Stringify(llParseUtf8)
+ loJSONStr = parser.Stringify(llParseUtf8, tlTrimChars)
catch to loEx
this.ShowExceptionError(loEx)
finally
@@ -148,10 +183,10 @@ define class JSONClass as session
&& Function Encode
&& <> please use Stringify function instead.
&& ======================================================================== &&
- function Encode(toObj as object, tcFlags as string) as memo
+ function Encode(toObj as object, tcFlags as string, tlUtf8 as Boolean, tlTrimChars as Boolean) as memo
local loEncode
loEncode = createobject("ObjectToJson")
- return loEncode.Encode(@toObj, tcFlags)
+ return loEncode.Encode(@toObj, tcFlags, tlUtf8, tlTrimChars)
endfunc
&& ======================================================================== &&
&& Function decode
@@ -203,9 +238,11 @@ define class JSONClass as session
try
this.ResetError()
=xmltocursor(tcXML, 'qXML')
- loParser = createobject("CursorToArray")
- loParser.CurName = "qXML"
+ loParser = createobject("CursorToArray")
+ loParser.CurName = "qXML"
loParser.nSessionID = set("Datasession")
+ loParser.ParseUTF8 = .T.
+ loParser.TrimChars = .T.
lcJsonXML = loParser.CursorToArray()
catch to loEx
this.ShowExceptionError(loEx)
@@ -218,7 +255,7 @@ define class JSONClass as session
endfunc
* CursorToJSON
function CursorToJSON as memo
- lparameters tcCursor as string, tbCurrentRow as Boolean, tnDataSession as integer, tlJustArray as Boolean
+ 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
lcJsonXML = ''
lcCursor = SYS(2015)
@@ -236,6 +273,12 @@ 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)
@@ -246,10 +289,69 @@ define class JSONClass as session
endtry
lcOutput = iif(tlJustArray, lcJsonXML, '{"' + lower(alltrim(tcCursor)) + '":' + lcJsonXML + '}')
return lcOutput
- endfunc
+ EndFunc
+
+ * CursorToJSONObject
+ function CursorToJSONObject(tcCursor as string, tbCurrentRow as Boolean, tnDataSession as integer) as object
+ local loParser, lcCursor, lnRecno, loResult as Variant
+ lcCursor = SYS(2015)
+ try
+ 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
+ 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
+ Try
+ 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
+ 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
+ Try
local lexer, parser
this.ResetError()
if !empty(tcCursor)
@@ -292,7 +394,7 @@ define class JSONClass as session
return lcOutput
endfunc
* tokenize
- function dumpTokens
+ function dumpTokens2
lparameters tcJsonStr as memo
try
this.ResetError()
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..15cda23 100644
--- a/src/jsonstringify.prg
+++ b/src/jsonstringify.prg
@@ -10,6 +10,7 @@
define class JSONStringify as custom
ParseUtf8 = .f.
+ TrimChars = .f.
Dimension tokens[1]
Hidden current
@@ -25,8 +26,10 @@ define class JSONStringify as custom
* Stringify
function Stringify as memo
- lparameters tlParseUtf8
+ lparameters tlParseUtf8, tlTrimChars
this.ParseUtf8 = tlParseUtf8
+ this.TrimChars = tlTrimChars
+
return this.value(0)
endfunc
&& ======================================================================== &&
@@ -62,9 +65,11 @@ 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 = this.previous.value
+ 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 +78,8 @@ 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(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
diff --git a/src/jsonutils.prg b/src/jsonutils.prg
index 5b81cc1..4c0396b 100644
--- a/src/jsonutils.prg
+++ b/src/jsonutils.prg
@@ -5,24 +5,45 @@
&& ======================================================================== &&
define class jsonutils as custom
- Dimension aPattern[5, 2]
+ 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
@@ -30,8 +51,8 @@ define class jsonutils as custom
&& ======================================================================== &&
&& Function GetValue
&& ======================================================================== &&
- function getvalue as string
- lparameters tcvalue as string, tctype as character
+ function getValue as string
+ lparameters tcvalue as string, tctype as character, tlParseUTF8 as Boolean, tlTrimChars as Boolean
do case
case tctype $ "CDTBGMQVWX"
do case
@@ -42,9 +63,12 @@ define class jsonutils as custom
Case tctype == 'X'
tcvalue = "null"
Otherwise
- tcvalue = this.getstring(tcvalue)
+*!* tcvalue = this.getstring(tcvalue)
+ tcValue = this.getString(Iif(tlTrimChars, Alltrim(tcValue), tcValue), tlParseUTF8)
endcase
- tcvalue = alltrim(tcvalue)
+ && IRODG 08/08/2023 Inicio
+ *tcvalue = alltrim(tcValue)
+ && IRODG 08/08/2023 Fin
case tctype $ "YFIN"
tcvalue = strtran(transform(tcvalue), ',', '.')
case tctype == 'L'
@@ -66,7 +90,7 @@ define class jsonutils as custom
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
@@ -99,10 +123,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 = {//::}
@@ -132,11 +163,16 @@ define class jsonutils as custom
&& ======================================================================== &&
function getstring as string
lparameters tcString as string, tlParseUtf8 as Boolean
- tcString = allt(tcString)
+ && 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
@@ -173,9 +209,12 @@ define class jsonutils as custom
tcString = StrTran(tcString,'','\u00a9')
tcString = StrTran(tcString,'','\u00ae')
tcString = StrTran(tcString,'','\u00e7')
- endif
-
- return '"' +tcString + '"'
+ tcString = StrTran(tcString,'','\u00ba')
+ EndIf
+ If Left(Alltrim(tcString), 1) != '"' and Right(Alltrim(tcString),1) != '"'
+ return '"'+tcString+'"'
+ EndIf
+ Return tcString
endfunc
&& ======================================================================== &&
&& Function CheckProp
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..4d6a032 100644
--- a/src/objecttojson.prg
+++ b/src/objecttojson.prg
@@ -6,6 +6,8 @@ define class ObjectToJSON as session
cDateAct = ''
nOrden = 0
cFlags = ''
+ parseUTF8 = .f.
+ TrimChars = .f.
* Function Init
function init
this.lCentury = set("Century") == "OFF"
@@ -15,7 +17,7 @@ define class ObjectToJSON as session
mvcount = 60000
endfunc
* Encode
- function Encode(toRefObj, tcFlags)
+ function Encode(toRefObj, tcFlags, tlParseUTF8, tlTrimChars)
lPassByRef = .t.
try
external array toRefObj
@@ -23,12 +25,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
@@ -46,8 +55,9 @@ define class ObjectToJSON as session
for k = 1 to alen(tValue)
lcArray = lcArray + iif(len(lcArray) > 1, ',', '')
try
+ local array laLista[1]
*local array aLista(alen(tValue[k]))
- =acopy(tValue[k], aLista)
+ =acopy(tValue[k], laLista)
lcArray = lcArray + this.AnyToJson(@aLista)
catch
lcArray = lcArray + this.AnyToJson(tValue[k])
@@ -67,7 +77,8 @@ define class ObjectToJSON as session
lcArray = lcArray + ','
endif
try
- =acopy(tValue[k, j], aLista)
+ local array laLista[1]
+ =acopy(tValue[k, j], laLista)
lcArray = lcArray + this.AnyToJson(@aLista)
catch
lcArray = lcArray + this.AnyToJson(tValue[k, j])
@@ -91,8 +102,9 @@ define class ObjectToJSON as session
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)
+ local array laLista[1]
+ =acopy(tValue. &gaMembers[j], laLista)
+ lcJSONStr = lcJSONStr + this.AnyToJson(@laLista)
catch
try
lcJSONStr = lcJSONStr + this.AnyToJson(tValue. &gaMembers[j])
@@ -121,7 +133,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..0820f19 100644
--- a/src/parser.prg
+++ b/src/parser.prg
@@ -14,18 +14,14 @@ define class Parser as custom
Hidden peek
function init(toScanner)
- With this
- Local laTokens
- laTokens = toScanner.scanTokens()
- =Acopy(laTokens, .tokens)
- .current = 1
- endwith
+ Local laTokens
+ laTokens = toScanner.scanTokens()
+ =Acopy(laTokens, this.tokens)
+ this.current = 1
endfunc
function Parse
- With this
- Return .value()
- endwith
+ Return this.value()
endfunc
&& ======================================================================== &&
&& Function Object
@@ -33,51 +29,41 @@ 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)
@@ -97,113 +83,94 @@ 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.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.tokens[this.current]
endfunc
Hidden Function previous_access
- With this
- Return .tokens[.current-1]
- endwith
+ Return this.tokens[this.current-1]
EndFunc
EndDefine
@@ -216,16 +183,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..7fe12ad 100644
--- a/src/tokenizer.prg
+++ b/src/tokenizer.prg
@@ -1,15 +1,3 @@
-* <>
-* =========================================
-*!* 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
@@ -26,11 +14,16 @@ define class Tokenizer as custom
Dimension tokens[1]
sourceLen = 0
-
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
.start = 0
.current = 1
@@ -116,7 +109,18 @@ define class Tokenizer as custom
do while isdigit(.peek())
.advance()
enddo
- EndIf
+ 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
@@ -185,33 +189,97 @@ define class Tokenizer as custom
endfunc
- 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 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 escapeCharacters(tcLexeme)
+ local lcResult, i, lcChar, lcNextChar
+ lcResult = ""
+ i = 1
- procedure checkUnicodeFormat(tcLexeme)
+ 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
+ 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
+ ** 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
+*!* _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
+*!* && IRODG 09/10/2023 Inicio
+*!* ** Replace null character (chr(0)) from conversion result (strconv(lcValue, 16))
+*!* *!* tcLexeme = Strtran(tcLexeme, lcValue, (strconv(lcValue, 16)))
+*!* tcLexeme = Strtran(tcLexeme, lcValue, strtran((strconv(lcValue, 16)), chr(0)))
+*!* && IRODG 09/10/2023 Fin
+*!* Catch
+*!* EndTry
+*!* EndFor
+*!* EndIf
EndProc
Function scanTokens