DBFCDX driver, array in memo field

This forum is meant for anything you would like to share with other visitors
Post Reply
User avatar
Jean3riv
Posts: 2
Joined: Sat Sep 26, 2015 11:57 am

DBFCDX driver, array in memo field

Post by Jean3riv »

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/
User avatar
robert
Posts: 4225
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

DBFCDX driver, array in memo field

Post by robert »

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
XSharp Development Team
The Netherlands
robert@xsharp.eu
Sherlock
Posts: 60
Joined: Mon Sep 28, 2015 1:37 pm
Location: Australia mate... fare dikkum

DBFCDX driver, array in memo field

Post by Sherlock »

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
Phil McGuinness
mainhatten
Posts: 200
Joined: Wed Oct 09, 2019 6:51 pm

DBFCDX driver, array in memo field

Post by mainhatten »

Hi Robert,
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/
reading up older stuff as I paused for a few months - in vfp we have something similar in save to / restore from memo syntax.
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
Post Reply