Welcome, Guest
Username: Password: Remember me
  • Page:
  • 1

TOPIC:

DBFCDX driver, array in memo field 30 May 2020 15:33 #14775

  • Jean3riv
  • Jean3riv's Avatar
  • Topic Author


  • Posts: 2
  • 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
    rayonline.com/

    Please Log in or Create an account to join the conversation.

    DBFCDX driver, array in memo field 30 May 2020 20:08 #14777

    • robert
    • robert's Avatar


  • Posts: 3293
  • 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

    Please Log in or Create an account to join the conversation.

    DBFCDX driver, array in memo field 31 May 2020 10:32 #14781

    • Sherlock
    • Sherlock's Avatar


  • Posts: 51
  • 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 // Something's not right, stop parsing
    ENDIF
    // ==========
    CASE SubStr(cPHPString, nNEXT, 2) == "s:" // String
    cTmp := SELF:ExtractTextBetweenMarkers(cPHPString, ":", ":", nNEXT, @nNEXT)
    IF SLen(cTmp) > 0
    nStrLen := DWORD(Val(cTmp)) // Get string length
    IF SubStr(cPHPString, nNEXT, 1) == '"' // Verification that the string starts with double quote
    nNEXT += 1
    cTmp := SubStr(cPHPString, nNEXT, nStrLen) // Note: we assume no escape characters here! BEWARE !!!!
    //
    IF (SLen(cTmp) >= 8 .AND. SLen(cTmp) <= 10 .AND. At("/", cTmp) > 0) .OR. SToD(cTmp) != NULL_DATE // Maybe a date - you may omit this branch if you don't want
    // IF (SLen(cTmp) >= 8 .AND. SLen(cTmp) <= 10 .AND. Occurs(cTmp, "/") = 2) .OR. SToD(cTmp) != NULL_DATE // Maybe a date - you may omit this branch if you don't want // PMG change 05/09/2014
    // to convert string to date
    IF SToD(DToS(CToD(cTmp))) != NULL_DATE
    // IF SToD(cTmp) != NULL_DATE
    // dtValue := SToD(cTmp)
    dtValue := SToD(DToS(CToD(cTmp)))
    ELSE
    dtValue := CToD(cTmp)
    ENDIF
    IF dtValue != NULL_DATE
    aOut[nIndex] := dtValue // Set as date
    ELSE
    aOut[nIndex] := cTmp // Set as string
    ENDIF
    ELSE
    aOut[nIndex] := cTmp // Set as string
    ENDIF
    nNEXT += nStrLen - 1
    nNEXT := At3('";', cPHPString, nNEXT) // Advance nNEXT - this works even if the string contained escape characters
    IF nNEXT > 0
    nNEXT += 2
    ENDIF
    ELSE
    lIsOK := FALSE // Something's not right, stop parsing
    ENDIF
    lTypeExpected := FALSE
    ELSE
    lIsOK := FALSE
    ENDIF
    //
    CASE SubStr(cPHPString, nNEXT, 2) == "a:" // Array
    cTmp := SELF:ExtractPHPArrayString(cPHPString, nNEXT, @nNEXT)
    IF SLen(cTmp) > 0
    aOut[nIndex] := SELF:DeserializePHPArray(cTmp) // This method is called recurrently - bear it on mind if you'll debug the code
    lTypeExpected := FALSE
    ELSE
    lIsOK := FALSE // Something's not right, stop parsing
    ENDIF
    OTHERWISE
    lIsOK := FALSE // Something's not right, stop parsing
    ENDCASE
    ENDIF
    //
    IF nNEXT == 0
    lIsOK := FALSE // Another paranoid code - in case no expected pattern detected,
    // end the sordid story
    ENDIF
    ENDDO
    ENDIF
    ENDIF
    //
    RETURN aOut
    METHOD ExtractPHPArrayString(cInput AS STRING, nStartPos AS DWORD, nNEXT REF DWORD) AS STRING PASCAL CLASS Convert2PHPStringArray

    LOCAL cOut := "" AS STRING
    LOCAL cOneChar
    LOCAL nLen, nBrackets AS DWORD
    LOCAL lInsideQuotes := FALSE AS LOGIC

    nNEXT := At3("{", cInput, nStartPos)

    IF nNEXT > 0
    nNEXT += 1
    nBrackets := 1
    nLen := SLen(cInput)
    //
    DO WHILE nNEXT <= nLen .AND. nBrackets > 0
    cOneChar := SubStr(cInput, nNEXT, 1)
    //
    IF cOneChar == '"'
    IF !lInsideQuotes
    lInsideQuotes := TRUE
    ELSE
    IF nNEXT < nLen
    IF SubStr(cInput, nNEXT + 1, 1) == ";"
    lInsideQuotes := FALSE
    ENDIF
    ENDIF
    ENDIF
    ELSEIF cOneChar == "{" .AND. ! lInsideQuotes
    nBrackets += 1
    ELSEIF cOneChar == "}" .AND. ! lInsideQuotes
    nBrackets -= 1
    ENDIF
    nNEXT += 1
    ENDDO
    //
    IF nBrackets == 0
    cOut := SubStr(cInput, nStartPos, nNEXT - nStartPos)
    ENDIF
    //
    IF nNEXT <= nLen
    IF SubStr(cInput, nNEXT, 1) == ";"
    nNEXT += 1
    ENDIF
    ENDIF
    ENDIF
    //
    RETURN cOut

    METHOD ExtractTextBetweenMarkers(cInput AS STRING, cStartMarker AS STRING, cEndMarker AS STRING, nStartPos AS DWORD, nNEXT REF DWORD) AS STRING PASCAL CLASS Convert2PHPStringArray

    LOCAL cOut := "" AS STRING
    LOCAL nStart AS DWORD

    nNEXT := 0
    IF SLen(cStartMarker) == 0
    nNEXT := nStartPos
    ELSE
    nNEXT := At3(cStartMarker, cInput, nStartPos)
    ENDIF

    IF nNEXT > 0
    nNEXT += SLen(cStartMarker)
    nStart := nNEXT

    IF SLen(cEndMarker) == 0
    nNEXT := SLen(cInput) + 1
    ELSE
    nNEXT := At3(cEndMarker, cInput, nNEXT)
    ENDIF

    IF nNEXT > 0
    cOut := SubStr(cInput, nStart, nNEXT - nStart)
    nNEXT += SLen(cEndMarker)
    ENDIF
    ENDIF
    //
    RETURN cOut

    ACCESS PHPString2Array AS ARRAY PASCAL CLASS Convert2PHPStringArray
    RETURN SELF:aDeserialized
    Phil McGuinness

    Please Log in or Create an account to join the conversation.

    DBFCDX driver, array in memo field 29 Nov 2020 16:15 #16790

    • mainhatten
    • mainhatten's Avatar


  • Posts: 199
  • 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

    Please Log in or Create an account to join the conversation.

    Last edit: by mainhatten.
    • Page:
    • 1