Welcome, Guest |
TOPIC:
DBFCDX driver, array in memo field 30 May 2020 15:33 #14775
|
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
|
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
|
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
|
Hi Robert,
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. |