Hello everybody
In cavo28 sp3, I can write and retrieve an array from the memo field.
In #Sharp, VO, I can only retrieve the array, the FieldPut() function return a crash application...
XSharp.Error
Unknown Error occurred
The question:
Is xSharp won't support anymore the array in a memo field, or it is just a bug?
Thank in advance for your cooperation...
PS:Driver DBFVFP is doing the same
Best regards
Jean Raymond
https://rayonline.com/
DBFCDX driver, array in memo field
DBFCDX driver, array in memo field
Jean,
For backward compatibility we added reading of arrays from memos.
However this format is so complicated that we did not add writing.
In a DotNet environment we recommend that you serialize your arrays to a string and store that string to the memo.
You can for example use the JSON format, which is supported by other languages as well/
Robert
For backward compatibility we added reading of arrays from memos.
However this format is so complicated that we did not add writing.
In a DotNet environment we recommend that you serialize your arrays to a string and store that string to the memo.
You can for example use the JSON format, which is supported by other languages as well/
Robert
XSharp Development Team
The Netherlands
robert@xsharp.eu
The Netherlands
robert@xsharp.eu
DBFCDX driver, array in memo field
JSON can be a bit a learn curve initially at least. This is Serialiser for Arrays works a treat. I still use and can also can be read natively by PHP. Enough examples in the code... Enjoy
ACCESS Array2PHPString AS STRING PASCAL CLASS Convert2PHPStringArray
RETURN SELF:cPHPString
CLASS Convert2PHPStringArray
PROTECT aProcessed := {} AS ARRAY // Phil McGuinness - APRIL, 2006
PROTECT cPHPString := "" AS STRING // cPHPString := Convert2PHPString{ aArray }:Array2PHPString
PROTECT nLenArrayLevel1 AS DWORD
PROTECT nLenArrayLevel2 AS DWORD
PROTECT aDeserialized := {} AS ARRAY
//
DECLARE METHOD DataType // Build aProcessed ARRAY with substrings for PHPString output
DECLARE ACCESS Array2PHPString // Return the PHPString [ ie a:2:{i:0;s:1:"a";i:1;a:3:{i:0;s:1:"c";i:1;s:1:"d";i:2;s:4:"e";}} ]
// Deserialization - Ales, 01/02/2013
DECLARE ACCESS PHPString2Array // Return the VO array
DECLARE METHOD DeserializePHPArray // Called internally if the argument in Init() is a string
DECLARE METHOD ExtractTextBetweenMarkers
DECLARE METHOD ExtractPHPArrayString
//
METHOD DataType( uWorking AS USUAL, nElement AS DWORD, nArrayElements AS DWORD, symLevel AS SYMBOL) AS VOID PASCAL CLASS Convert2PHPStringArray
LOCAL cSubstring, cType := [s:] AS STRING
LOCAL cPreFix := "", cPostFix := "" AS STRING
LOCAL nItems AS DWORD
DO CASE
CASE IsLogic( uWorking )
cType := [b:] + IIF(uWorking, "1","0")
//
CASE IsString( uWorking )
// IF SLen(uWorking) <= 10 .AND. CToD(uWorking) != NULL_DATE // 02:10 PM passes as CTOD() date, and the date s:10:"02/01/0349"
IF SLen(uWorking) <= 10 .AND. CToD(uWorking) != NULL_DATE .AND. Occurs(uWorking, "/") = 2 // Change PMG 05/09/2014
cSubstring := DToC(CToD(uWorking))
ELSE
cSubstring := AllTrim(StrTran(uWorking, ["], _CHR(32) )) // Stringify result
ENDIF
cType := [s:] + NTrim(SLen(cSubstring)) + [:"] + cSubstring + ["]
//
CASE IsNumeric( uWorking )
cSubstring := AllTrim(AsString(uWorking)) // Stringify result
cType := IIF( Frac(uWorking) != 0, [d:] , [i:] ) + cSubstring
//
CASE IsDate( uWorking )
cSubstring := DToS(uWorking)
cType := [s:] + NTrim(SLen(cSubstring)) + [:"] + cSubstring + ["]
//
OTHERWISE
cSubstring := AllTrim(AsString(uWorking)) // Stringify result
ENDCASE
//
IF InList( symLevel, #LEVEL1, #LEVEL2 )
//
DO CASE
CASE symLevel = #LEVEL1 ; nItems := SELF:nLenArrayLevel1
CASE symLevel = #LEVEL2 ; nItems := SELF:nLenArrayLevel2
ENDCASE
//
IF nElement = 1
cPreFix := "i:" + AsString( nArrayElements - 1 ) + ";a:" + AsString( nItems ) + ":{"
//
ELSEIF nElement = nItems
cPostFix := "}"
ENDIF
ENDIF
//
AAdd( SELF:aProcessed, cPreFix + [i:] + AsString(nElement-1) + ";" + cType + ";" + cPostFix )
//
RETURN
METHOD Init( uUserData ) CLASS Convert2PHPStringArray
LOCAL nLenArray, xx, yy, zz AS DWORD // Allow for Single Dimension and up to 3 dimension array.
IF IsArray( uUserData )
nLenArray := ALen( uUserData )
SELF:cPHPString := [a:] + AsString( nLenArray ) + [:] // a:6:
SELF:cPHPString += '{'
//
FOR xx := 1 TO nLenArray // ?? elements
IF IsArray( uUserData[xx] )
SELF:nLenArrayLevel1 := ALen( uUserData[xx] )
//
FOR yy := 1 TO SELF:nLenArrayLevel1 // ?? elements
SELF:DataType( uUserData[xx][yy], yy, xx, #LEVEL1 )
IF IsArray( uUserData[xx][yy] )
SELF:nLenArrayLevel2 := ALen( uUserData[xx][yy] )
//
FOR zz := 1 TO SELF:nLenArrayLevel2 // ?? elements
SELF:DataType( uUserData[xx][yy][zz], zz, yy, #LEVEL2 )
NEXT
SELF:nLenArrayLevel2 := 0
ENDIF
NEXT
SELF:nLenArrayLevel1 := 0
ELSE
SELF:DataType( uUserData[xx], xx, xx, #LEVEL0 )
ENDIF
NEXT
//
FOR xx := 1 TO ALen( SELF:aProcessed )
SELF:cPHPString += SELF:aProcessed[xx]
NEXT
SELF:cPHPString += '}'
SELF:aProcessed := {}
//
ELSEIF IsString( uUserData )
SELF:aDeserialized := SELF:DeserializePHPArray(AsString(uUserData))
ENDIF
//
RETURN SELF
// FUNCTION Start() AS STRING
// LOCAL aArray AS ARRAY
// aArray := { 1, "2", "2/03/2006", 1.234 }
// aArray := { {"a","b","c"},{"d","e","f"}}
// aArray := { "a",{ "c","d","e" } }
//
// cPHPString := Convert2PHPStringArray{ aArray }:Array2PHPString
// ==========================
// aArray := { 1, "2", "2/03/2006", 1.234 } // INPUT
// a:4:{i:0;i:1;i:1;s:1:"2";i:2;s:9:"2/03/2006";i:3;d:1.24;} // OUTPUT
// ==========================
// aArray := { {"a","b","c"},{"d","e","f"}} // INPUT
// a:2:{i:0;a:3:{i:0;s:1:"a";i:1;s:1:"b";i:2;s:1:"c";}i:1;a:3:{i:0;s:1:"d";i:1;s:1:"e";i:2;s:1:"f";}} // OUTPUT
// ==========================
// aArray := { { "a",{ "c","d","e" }} } // INPUT
// a:2:{i:0;s:1:"a";i:1;a:3:{i:0;s:1:"c";i:1;s:1:"d";i:2;s:4:"e";}} // OUTPUT
// ==========================
// MemoWrit( "c:" + 'phptest.txt', cPhpString)
// ShellExecute( NULL, String2Psz("open"), String2Psz("notepad.exe"), String2Psz("c:" + 'phptest.txt'), NULL, SW_SHOWNORMAL )
//
// aArray := xConvert2PHPStringArray{ 'a:2:{i:0;a:3:{i:0;s:1:"a";i:1;s:1:"b";i:2;s:1:"c";}i:1;a:3:{i:0;s:1:"d";i:1;s:1:"e";i:2;s:1:"f";}}' }:PHPString2Array
//
// RETURN NIL
// But this works:
//x := xConvert2PHPStringArray{ 'a:4:{i:0;i:1;i:1;s:1:"2";i:2;s:9:"2/03/2006";i:3;d:1.24;}' }
//x := xConvert2PHPStringArray{ 'a:2:{i:0;s:2:"AB";i:1;s:2:"CD";}' }
//x := xConvert2PHPStringArray{ 'a:6:{i:0;i:33;i:1;d:12.53999999999999914734871708787977695465087890625;i:2;s:4:"strA";i:3;s:3:"q"q";i:4;s:3:"w"w";i:5;s:6:"abcdef";}' }
//x := xConvert2PHPStringArray{ 'a:2:{i:0;a:3:{i:0;s:1:"a";i:1;s:1:"b";i:2;s:1:"c";}i:1;a:3:{i:0;s:1:"d";i:1;s:1:"e";i:2;s:1:"f";}}' }
// aArray := x:PHPString2Array
// aArray := { 2, 3, 4 }
// aArray := { 1, "2", "2/03/2006", 1.234 }
// aArray := { {"a","b","c"},{"d","e","f"}}
// aArray := { "a",{ "c","d","e" } }
//
METHOD DeserializePHPArray(cPHPString AS STRING) AS ARRAY PASCAL CLASS Convert2PHPStringArray
LOCAL aOut := { } AS ARRAY
LOCAL nArrlen, nLen, nNEXT, nIndex, nValue, nStrLen AS DWORD
LOCAL iValue AS LONGINT
LOCAL rValue AS REAL8
LOCAL lTypeExpected := FALSE AS LOGIC
LOCAL lIsOK := TRUE AS LOGIC
LOCAL cTmp AS STRING
LOCAL dtValue AS DATE
IF SubStr(cPHPString, 1, 2) == "a:" // Validation if the PHP array really starts with "a:"
nLen := SLen(cPHPString)
cTmp := SELF:ExtractTextBetweenMarkers(cPHPString, ":", ":{", 1, @nNEXT)
//
IF SLen(cTmp) > 0 // Notice my paranoia
nArrlen := DWORD(Val(cTmp)) // Getting the number of a PHP array members
aOut := ArrayCreate(nArrlen)
//
DO WHILE nNEXT <= nLen .AND. lIsOK
//
IF lTypeExpected == FALSE
IF SubStr(cPHPString, nNEXT, 1) == "}" // End of current array - stop parsing
lIsOK := FALSE
//
ELSEIF SubStr(cPHPString, nNEXT, 2) == "i:" // Index
cTmp := SELF:ExtractTextBetweenMarkers(cPHPString, ":", ";", nNEXT, @nNEXT)
IF SLen(cTmp) > 0
nIndex := DWORD(Val(cTmp)) + 1 // PHP index starts with 0, VO starts with 1
lTypeExpected := TRUE
ELSE
lIsOK := FALSE // Can't get an index
ENDIF
ELSE
lIsOK := FALSE // Something's not right, stop parsing
ENDIF
ELSE // This branch deals with types
DO CASE
CASE SubStr(cPHPString, nNEXT, 2) == "i:" // Integer or DWORD
cTmp := SELF:ExtractTextBetweenMarkers(cPHPString, ":", ";", nNEXT, @nNEXT)
IF SLen(cTmp) > 0
iValue := LONGINT(Val(cTmp))
IF iValue < 0 // If it's less than zero, store it as LONGINT, otherwise as DWORD
aOut[nIndex] := iValue
ELSE
nValue := DWORD(iValue)
aOut[nIndex] := nValue
ENDIF
lTypeExpected := FALSE
ELSE
lIsOK := FALSE // Something's not right, stop parsing
ENDIF
// ==========
CASE SubStr(cPHPString, nNEXT, 2) == "d:" // Double
cTmp := SELF:ExtractTextBetweenMarkers(cPHPString, ":", ";", nNEXT, @nNEXT)
IF SLen(cTmp) > 0
rValue := Val(cTmp)
aOut[nIndex] := rValue // Store as REAL8
lTypeExpected := FALSE
ELSE
lIsOK := FALSE // Somethi
ACCESS Array2PHPString AS STRING PASCAL CLASS Convert2PHPStringArray
RETURN SELF:cPHPString
CLASS Convert2PHPStringArray
PROTECT aProcessed := {} AS ARRAY // Phil McGuinness - APRIL, 2006
PROTECT cPHPString := "" AS STRING // cPHPString := Convert2PHPString{ aArray }:Array2PHPString
PROTECT nLenArrayLevel1 AS DWORD
PROTECT nLenArrayLevel2 AS DWORD
PROTECT aDeserialized := {} AS ARRAY
//
DECLARE METHOD DataType // Build aProcessed ARRAY with substrings for PHPString output
DECLARE ACCESS Array2PHPString // Return the PHPString [ ie a:2:{i:0;s:1:"a";i:1;a:3:{i:0;s:1:"c";i:1;s:1:"d";i:2;s:4:"e";}} ]
// Deserialization - Ales, 01/02/2013
DECLARE ACCESS PHPString2Array // Return the VO array
DECLARE METHOD DeserializePHPArray // Called internally if the argument in Init() is a string
DECLARE METHOD ExtractTextBetweenMarkers
DECLARE METHOD ExtractPHPArrayString
//
METHOD DataType( uWorking AS USUAL, nElement AS DWORD, nArrayElements AS DWORD, symLevel AS SYMBOL) AS VOID PASCAL CLASS Convert2PHPStringArray
LOCAL cSubstring, cType := [s:] AS STRING
LOCAL cPreFix := "", cPostFix := "" AS STRING
LOCAL nItems AS DWORD
DO CASE
CASE IsLogic( uWorking )
cType := [b:] + IIF(uWorking, "1","0")
//
CASE IsString( uWorking )
// IF SLen(uWorking) <= 10 .AND. CToD(uWorking) != NULL_DATE // 02:10 PM passes as CTOD() date, and the date s:10:"02/01/0349"
IF SLen(uWorking) <= 10 .AND. CToD(uWorking) != NULL_DATE .AND. Occurs(uWorking, "/") = 2 // Change PMG 05/09/2014
cSubstring := DToC(CToD(uWorking))
ELSE
cSubstring := AllTrim(StrTran(uWorking, ["], _CHR(32) )) // Stringify result
ENDIF
cType := [s:] + NTrim(SLen(cSubstring)) + [:"] + cSubstring + ["]
//
CASE IsNumeric( uWorking )
cSubstring := AllTrim(AsString(uWorking)) // Stringify result
cType := IIF( Frac(uWorking) != 0, [d:] , [i:] ) + cSubstring
//
CASE IsDate( uWorking )
cSubstring := DToS(uWorking)
cType := [s:] + NTrim(SLen(cSubstring)) + [:"] + cSubstring + ["]
//
OTHERWISE
cSubstring := AllTrim(AsString(uWorking)) // Stringify result
ENDCASE
//
IF InList( symLevel, #LEVEL1, #LEVEL2 )
//
DO CASE
CASE symLevel = #LEVEL1 ; nItems := SELF:nLenArrayLevel1
CASE symLevel = #LEVEL2 ; nItems := SELF:nLenArrayLevel2
ENDCASE
//
IF nElement = 1
cPreFix := "i:" + AsString( nArrayElements - 1 ) + ";a:" + AsString( nItems ) + ":{"
//
ELSEIF nElement = nItems
cPostFix := "}"
ENDIF
ENDIF
//
AAdd( SELF:aProcessed, cPreFix + [i:] + AsString(nElement-1) + ";" + cType + ";" + cPostFix )
//
RETURN
METHOD Init( uUserData ) CLASS Convert2PHPStringArray
LOCAL nLenArray, xx, yy, zz AS DWORD // Allow for Single Dimension and up to 3 dimension array.
IF IsArray( uUserData )
nLenArray := ALen( uUserData )
SELF:cPHPString := [a:] + AsString( nLenArray ) + [:] // a:6:
SELF:cPHPString += '{'
//
FOR xx := 1 TO nLenArray // ?? elements
IF IsArray( uUserData[xx] )
SELF:nLenArrayLevel1 := ALen( uUserData[xx] )
//
FOR yy := 1 TO SELF:nLenArrayLevel1 // ?? elements
SELF:DataType( uUserData[xx][yy], yy, xx, #LEVEL1 )
IF IsArray( uUserData[xx][yy] )
SELF:nLenArrayLevel2 := ALen( uUserData[xx][yy] )
//
FOR zz := 1 TO SELF:nLenArrayLevel2 // ?? elements
SELF:DataType( uUserData[xx][yy][zz], zz, yy, #LEVEL2 )
NEXT
SELF:nLenArrayLevel2 := 0
ENDIF
NEXT
SELF:nLenArrayLevel1 := 0
ELSE
SELF:DataType( uUserData[xx], xx, xx, #LEVEL0 )
ENDIF
NEXT
//
FOR xx := 1 TO ALen( SELF:aProcessed )
SELF:cPHPString += SELF:aProcessed[xx]
NEXT
SELF:cPHPString += '}'
SELF:aProcessed := {}
//
ELSEIF IsString( uUserData )
SELF:aDeserialized := SELF:DeserializePHPArray(AsString(uUserData))
ENDIF
//
RETURN SELF
// FUNCTION Start() AS STRING
// LOCAL aArray AS ARRAY
// aArray := { 1, "2", "2/03/2006", 1.234 }
// aArray := { {"a","b","c"},{"d","e","f"}}
// aArray := { "a",{ "c","d","e" } }
//
// cPHPString := Convert2PHPStringArray{ aArray }:Array2PHPString
// ==========================
// aArray := { 1, "2", "2/03/2006", 1.234 } // INPUT
// a:4:{i:0;i:1;i:1;s:1:"2";i:2;s:9:"2/03/2006";i:3;d:1.24;} // OUTPUT
// ==========================
// aArray := { {"a","b","c"},{"d","e","f"}} // INPUT
// a:2:{i:0;a:3:{i:0;s:1:"a";i:1;s:1:"b";i:2;s:1:"c";}i:1;a:3:{i:0;s:1:"d";i:1;s:1:"e";i:2;s:1:"f";}} // OUTPUT
// ==========================
// aArray := { { "a",{ "c","d","e" }} } // INPUT
// a:2:{i:0;s:1:"a";i:1;a:3:{i:0;s:1:"c";i:1;s:1:"d";i:2;s:4:"e";}} // OUTPUT
// ==========================
// MemoWrit( "c:" + 'phptest.txt', cPhpString)
// ShellExecute( NULL, String2Psz("open"), String2Psz("notepad.exe"), String2Psz("c:" + 'phptest.txt'), NULL, SW_SHOWNORMAL )
//
// aArray := xConvert2PHPStringArray{ 'a:2:{i:0;a:3:{i:0;s:1:"a";i:1;s:1:"b";i:2;s:1:"c";}i:1;a:3:{i:0;s:1:"d";i:1;s:1:"e";i:2;s:1:"f";}}' }:PHPString2Array
//
// RETURN NIL
// But this works:
//x := xConvert2PHPStringArray{ 'a:4:{i:0;i:1;i:1;s:1:"2";i:2;s:9:"2/03/2006";i:3;d:1.24;}' }
//x := xConvert2PHPStringArray{ 'a:2:{i:0;s:2:"AB";i:1;s:2:"CD";}' }
//x := xConvert2PHPStringArray{ 'a:6:{i:0;i:33;i:1;d:12.53999999999999914734871708787977695465087890625;i:2;s:4:"strA";i:3;s:3:"q"q";i:4;s:3:"w"w";i:5;s:6:"abcdef";}' }
//x := xConvert2PHPStringArray{ 'a:2:{i:0;a:3:{i:0;s:1:"a";i:1;s:1:"b";i:2;s:1:"c";}i:1;a:3:{i:0;s:1:"d";i:1;s:1:"e";i:2;s:1:"f";}}' }
// aArray := x:PHPString2Array
// aArray := { 2, 3, 4 }
// aArray := { 1, "2", "2/03/2006", 1.234 }
// aArray := { {"a","b","c"},{"d","e","f"}}
// aArray := { "a",{ "c","d","e" } }
//
METHOD DeserializePHPArray(cPHPString AS STRING) AS ARRAY PASCAL CLASS Convert2PHPStringArray
LOCAL aOut := { } AS ARRAY
LOCAL nArrlen, nLen, nNEXT, nIndex, nValue, nStrLen AS DWORD
LOCAL iValue AS LONGINT
LOCAL rValue AS REAL8
LOCAL lTypeExpected := FALSE AS LOGIC
LOCAL lIsOK := TRUE AS LOGIC
LOCAL cTmp AS STRING
LOCAL dtValue AS DATE
IF SubStr(cPHPString, 1, 2) == "a:" // Validation if the PHP array really starts with "a:"
nLen := SLen(cPHPString)
cTmp := SELF:ExtractTextBetweenMarkers(cPHPString, ":", ":{", 1, @nNEXT)
//
IF SLen(cTmp) > 0 // Notice my paranoia
nArrlen := DWORD(Val(cTmp)) // Getting the number of a PHP array members
aOut := ArrayCreate(nArrlen)
//
DO WHILE nNEXT <= nLen .AND. lIsOK
//
IF lTypeExpected == FALSE
IF SubStr(cPHPString, nNEXT, 1) == "}" // End of current array - stop parsing
lIsOK := FALSE
//
ELSEIF SubStr(cPHPString, nNEXT, 2) == "i:" // Index
cTmp := SELF:ExtractTextBetweenMarkers(cPHPString, ":", ";", nNEXT, @nNEXT)
IF SLen(cTmp) > 0
nIndex := DWORD(Val(cTmp)) + 1 // PHP index starts with 0, VO starts with 1
lTypeExpected := TRUE
ELSE
lIsOK := FALSE // Can't get an index
ENDIF
ELSE
lIsOK := FALSE // Something's not right, stop parsing
ENDIF
ELSE // This branch deals with types
DO CASE
CASE SubStr(cPHPString, nNEXT, 2) == "i:" // Integer or DWORD
cTmp := SELF:ExtractTextBetweenMarkers(cPHPString, ":", ";", nNEXT, @nNEXT)
IF SLen(cTmp) > 0
iValue := LONGINT(Val(cTmp))
IF iValue < 0 // If it's less than zero, store it as LONGINT, otherwise as DWORD
aOut[nIndex] := iValue
ELSE
nValue := DWORD(iValue)
aOut[nIndex] := nValue
ENDIF
lTypeExpected := FALSE
ELSE
lIsOK := FALSE // Something's not right, stop parsing
ENDIF
// ==========
CASE SubStr(cPHPString, nNEXT, 2) == "d:" // Double
cTmp := SELF:ExtractTextBetweenMarkers(cPHPString, ":", ";", nNEXT, @nNEXT)
IF SLen(cTmp) > 0
rValue := Val(cTmp)
aOut[nIndex] := rValue // Store as REAL8
lTypeExpected := FALSE
ELSE
lIsOK := FALSE // Somethi
Phil McGuinness
-
- Posts: 200
- Joined: Wed Oct 09, 2019 6:51 pm
DBFCDX driver, array in memo field
Hi Robert,
It is not widely used - sometimes in error handlers, where a transformation to saving strings/JSON might cost runtime, but in the event of error irrelevant and even good insofar as the memo text could be extracted with little effort.
Other uses were very specific, for instance transferring parts of the variable stack into a daughter process method via OLE Embedding and Linking - not a scenario often expected, but a fast shortcut when "clean" methods failed or took to long.
I don't expect you will hear many people asking for that feature very soon - those fox heads knowing about it and where it is/was useful probably can find a new way in DotNet
regards
thomas
reading up older stuff as I paused for a few months - in vfp we have something similar in save to / restore from memo syntax.robert wrote:For backward compatibility we added reading of arrays from memos.
However this format is so complicated that we did not add writing.
In a DotNet environment we recommend that you serialize your arrays to a string and store that string to the memo.
You can for example use the JSON format, which is supported by other languages as well/
It is not widely used - sometimes in error handlers, where a transformation to saving strings/JSON might cost runtime, but in the event of error irrelevant and even good insofar as the memo text could be extracted with little effort.
Other uses were very specific, for instance transferring parts of the variable stack into a daughter process method via OLE Embedding and Linking - not a scenario often expected, but a fast shortcut when "clean" methods failed or took to long.
I don't expect you will hear many people asking for that feature very soon - those fox heads knowing about it and where it is/was useful probably can find a new way in DotNet
regards
thomas