Page 2 of 3
StrToFile ( cExpression, cFileName [, lAdditive | nFlag])
Posted: Mon May 25, 2020 11:37 am
by Karl-Heinz
Hi Antonio,
You can throw a exception in this way, assuming you change the Flag type from INT to DWORD - otherwise you must also check for values < 0
Code: Select all
IF Flag == 3 .OR. Flag > 4
// valid Flag values are 0,1,2 or 4
THROW ArgumentException { String.Format("Param value is {0}, but it must be either 0,1,2 or 4", Flag ) , NAMEOF ( Flag ) }
ENDIF
regards
Karl-Heinz
StrToFile ( cExpression, cFileName [, lAdditive | nFlag])
Posted: Mon May 25, 2020 5:13 pm
by atlopes
Chris, Robert, and Karl-Heinz
Thank you for all the replies; your points were taken.
Regarding error handling, what I was looking specifically for is the defined list of exceptions in the X# namespaces, if there are any, so as not to derive redundant exception classes.
As for the encoding, Robert, the suggested implementation tries to mimic the VFP behavior. As I said above in this thread, the STRTOFILE() must not touch the string contents that are assumed to being binary in nature.
So, this should be possible, and the result should be an exact bit-by-bit copy of the original file.
Code: Select all
pngFileName = "<some PNG file>"
pngImage = FILETOSTR(pngFilename)
STRTOFILE(pngImage, JUSTPATH(pngFilename) + "Copy of " + JUSTFNAME(pngFilename), 0)
This works in VFP, should also work in X# as it is (we still have the Flags parameter to enhance the features of the function, including the ability to encode the string according to some Unicode format). As far as I tested, the implementation is producing the copy as required, but I might not have submitted the results to proper stress tests.
StrToFile ( cExpression, cFileName [, lAdditive | nFlag])
Posted: Mon May 25, 2020 8:53 pm
by robert
Antonio,
If you want to make an exact copy of a binary file in a unicode environment the best you can do is to read the file as a list of bytes and then write it as a list of bytes as well.
https://docs.microsoft.com/en-us/dotnet ... adallbytes
https://docs.microsoft.com/en-us/dotnet ... teallbytes
Robert
StrToFile ( cExpression, cFileName [, lAdditive | nFlag])
Posted: Mon May 25, 2020 10:31 pm
by atlopes
Robert, I don't want to make an exact copy of a binary file as a use case, but such capability illustrates the behavior of the VFP FILETOSTR() and STRTOFILE() functions. If you're targeting VFP compatibility, I assume you would want to see the behavior replicated (mainly because the functions are not part of other vocabularies).
As for reading and writing raw data files, I thought that XSharp.Core.FRead() and FWrite() would provide the basis for the functionality (and I would guess that they are calling the relevant .Net methods). In fact, there are no conceptual differences between the implementation I suggested and the examples to be found in the FWrite() documentation.
https://www.xsharp.eu/runtimehelp/html/ ... FWrite.htm
StrToFile ( cExpression, cFileName [, lAdditive | nFlag])
Posted: Tue May 26, 2020 6:43 am
by robert
Antonio,
I understand what you are trying to do.
However when you use Fread() and FWrite() with "buffers" of type STRING then you are introducing a complexity in a unicode environment: the runtime can't know if you expect the data in the files to be of type STRING or of type BINARY. And when the type is STRING it needs to know if you expect the runtime to convert to/from Unicode.
Many people (at least in the VO side of XBase) have used functions such as FRead, FWrite, FreadLine() and FWriteLine() to read/write Ansi text files. They expect that the runtime does an automatic conversion from the Ansi text to Unicode.
If you look at the FoxPro example for FREAD you will also see that it is used for reading TEXT files:
Code: Select all
Local gnFileHandle,nSize,cString
gnFileHandle = FOPEN("test.txt")
* Seek to end of file to determine number of bytes in the file.
nSize = FSEEK(gnFileHandle, 0, 2) && Move pointer to EOF
IF nSize <= 0
* If file is empty, display an error message.
WAIT WINDOW "This file is empty!" NOWAIT
ELSE
* If file is not empty, store the file's contents in memory
* and display the text in the main Visual FoxPro window.
= FSEEK(gnFileHandle, 0, 0) && Move pointer to BOF
cString = FREAD(gnFileHandle, nSize)
? cString
ENDIF
= FCLOSE(gnFileHandle) && Close the file
So this code also has to convert Ansi text to Unicode.
So what I would advise is:
- To read TEXT files, use a buffer of type string and use FRead() and FWrite()
- to read Binary files allocate a buffer with MemAlloc or use an array of bytes and use Fread3() and FWrite3()
Robert
StrToFile ( cExpression, cFileName [, lAdditive | nFlag])
Posted: Tue May 26, 2020 5:29 pm
by atlopes
Robert,
The STRTOFILE() and FILETOSTR() functions have only to make sure to send the contents of the string to the file, and vice-versa. Of course, when the string system is single-byte that is fairly simple to accomplish (the example you presented could use a "test.gif" file, in VFP it wouldn't make a difference). It's not that simple when the character system moves to Unicode, as you pointed out, and to which I agree.
I propose to extend the functionalities of both functions by adding other flags. In this way, the VFP behavior can still be fully supported, as well as more sensible options to easily write and read Unicode text files.
I combined the code from Kar-Heinz with mine - if that is ok. Other issues must still be addressed down the road, like NULL handling, but I think that this is a fairly regular VFP-compatible implementation of the two functions that also introduces new features on the side.
Code: Select all
* VFP standard flags
#DEFINE S2F_FLAG_OVERWRITE 0x0000
#DEFINE S2F_FLAG_APPEND 0x0001
#DEFINE S2F_FLAG_UNICODE_LE 0x0002
#DEFINE S2F_FLAG_UTF8 0x0004
* X# extension flags
#DEFINE S2F_FLAG_UNICODE_BE 0x0008
#define S2F_FLAG_UNICODE_FORMATS (S2F_FLAG_UNICODE_LE | S2F_FLAG_UTF8 | S2F_FLAG_UNICODE_BE)
#DEFINE S2F_FLAG_UNICODE_TEXT 0x0100
FUNCTION StrToFile (Expression AS String, Filename AS String, Flags AS Int) AS Int
LOCAL Additive = .F. AS Boolean
LOCAL BOM = "" AS String
LOCAL Result = 0 AS Int
LOCAL FHandle AS Int
LOCAL VFPBehavior = .T. AS Boolean // it means the string must hold an already prepared buffer, or it is binary
LOCAL UnicodeEncoding AS System.Text.Encoding
DO CASE
CASE Flags = S2F_FLAG_APPEND
Additive = .T.
CASE Flags = S2F_FLAG_UNICODE_LE
BOM = e"xFFxFE"
CASE Flags = S2F_FLAG_UTF8
BOM = e"xEFxBBxBF"
CASE Flags = S2F_FLAG_UNICODE_BE
BOM = e"xFExFF"
CASE Flags != S2F_FLAG_OVERWRITE
IF (Flags & S2F_FLAG_UNICODE_TEXT) != 0
VFPBehavior = .F.
Additive = (Flags & S2F_FLAG_APPEND) != 0
SWITCH Flags & S2F_FLAG_UNICODE_FORMATS
CASE S2F_FLAG_UNICODE_LE
UnicodeEncoding = System.Text.Encoding.Unicode
CASE S2F_FLAG_UTF8
UnicodeEncoding = System.Text.Encoding.UTF8
CASE S2F_FLAG_UNICODE_BE
UnicodeEncoding = System.Text.Encoding.BigEndianUnicode
OTHERWISE
THROW ArgumentException {}
END SWITCH
ELSE
THROW ArgumentException {}
ENDIF
END CASE
IF Additive
IF VFPBehavior
FHandle = FOpen(Filename, FO_READWRITE + FO_SHARED)
IF FHandle != F_ERROR
FSeek3(FHandle, 0, FS_END)
Result = FWrite(FHandle, Expression)
FClose(FHandle)
ENDIF
ELSE
TRY
File.AppendAllText(Filename, Expression, UnicodeEncoding)
CATCH
THROW
ENDTRY
Result = Expression:Length
ENDIF
ELSE
IF VFPBehavior
FHandle = FCreate(Filename)
IF FHandle != F_ERROR
IF ! (BOM == "")
Result = FWrite(FHandle, BOM)
ENDIF
Result += FWrite(FHandle, Expression)
FClose(FHandle)
ENDIF
ELSE
TRY
File.WriteAllText(Filename, Expression, UnicodeEncoding)
CATCH
THROW
ENDTRY
Result = Expression:Length
ENDIF
ENDIF
RETURN Result
ENDFUNC
FUNCTION FileToStr (Filename AS String) AS String
RETURN FileToStr(Filename, 0)
FUNCTION FileToStr (Filename AS String, Flags AS Int) AS String
LOCAL FHandle AS Int
LOCAL StrLen AS Int
LOCAL Result = .NULL. AS String
IF (Flags & S2F_FLAG_UNICODE_TEXT) = 0 // VFP behavior, read file as binary, even if it is a Unicode text
FHandle = FOpen(Filename, FO_READ + FO_SHARED)
IF FHandle != F_ERROR
StrLen = FSize(FHandle)
IF StrLen > 0
Result = SPACE(StrLen)
IF FRead(FHandle, @Result, StrLen) != StrLen
Result = .NULL.
ENDIF
ELSE
Result = ""
ENDIF
ENDIF
ELSE
TRY
Result = File.ReadAllText(Filename) // read a text file
CATCH
THROW
ENDTRY
ENDIF
RETURN Result
ENDFUNC
StrToFile ( cExpression, cFileName [, lAdditive | nFlag])
Posted: Wed May 27, 2020 7:49 pm
by Karl-Heinz
Hi Antonio,
i think your defines should be declared in this way
Code: Select all
* VFP standard flags
DEFINE S2F_FLAG_OVERWRITE = 0x0000
DEFINE S2F_FLAG_APPEND = 0x0001
DEFINE S2F_FLAG_UNICODE_LE = 0x0002
DEFINE S2F_FLAG_UTF8 = 0x0004
* X# extension flags
DEFINE S2F_FLAG_UNICODE_BE = 0x0008
DEFINE S2F_FLAG_UNICODE_FORMATS = (S2F_FLAG_UNICODE_LE | S2F_FLAG_UTF8 | S2F_FLAG_UNICODE_BE)
DEFINE S2F_FLAG_UNICODE_TEXT = 0x0100
I can´t comment in which situation VFP throws an runtime error or surpresses the error and sets maybe an VFP internal errorcode instead ? , but when i look at the places where you currently want to throw an error you should change that to:
Code: Select all
THROW ArgumentException { String.Format("Param value {0} is invalid" , Flags ) , NAMEOF ( Flags ) }
and
Code: Select all
TRY
File.AppendAllText(Filename, Expression, UnicodeEncoding)
CATCH e AS Exception
THROW e
ENDTRY
X# File functions like Fcreate() don´t throw an runtime error. If such a func fails the dos errorcode and the *
exception* is stored in the RuntimeState object. When i run this code your function returns 0, but i´m able to see why Fcreate() failed:
Code: Select all
? StrToFile ( "Drive 'P' doesn´t exist !" , "P:test.txt" , S2F_FLAG_UTF8 )
IF RuntimeState.FileError > 0
? RuntimeState.FileError , DosErrString ( RuntimeState.FileError )
?
IF RuntimeState.FileException != NULL
THROW RuntimeState.FileException
ENDIF
ENDIF
i see the errorcode 3, the errorcode description and the exception. This exception could also be thrown within your StrToFile() function e.g.
Code: Select all
...
FHandle = FCreate(Filename)
IF FHandle != F_ERROR
IF ! (BOM == "")
Result = FWrite(FHandle, BOM)
ENDIF
Result += FWrite(FHandle, Expression)
FClose(FHandle)
ENDIF
IF RuntimeState.FileError > 0 .AND. RuntimeState.FileException != NULL
THROW RuntimeState.FileException
ENDIF
...
All depends on what your VFP does if an invalid param is used or a file operation fails.
P.S. Forgot to mention FError(). This function returns the current RuntimeState.FileError value.
regards
Karl-Heinz
StrToFile ( cExpression, cFileName [, lAdditive | nFlag])
Posted: Sat May 30, 2020 12:14 pm
by atlopes
Karl-Heinz, thank you for your remarks.
i think your defines should be declared in this way
Code: Select all
* VFP standard flags
DEFINE S2F_FLAG_OVERWRITE = 0x0000
Ok, but why? (seriously, I'm trying to learn as much as I can from these interactions).
when i look at the places where you currently want to throw an error you should change that to:
Code: Select all
THROW ArgumentException { String.Format("Param value {0} is invalid" , Flags ) , NAMEOF ( Flags ) }
Wouldn't it be more appropriate to have these error messages centralized somewhere? Things like i18n will be harder to accomplish another way, and it was also for that I have asked about X# error handling guidelines.
and
Code: Select all
TRY
File.AppendAllText(Filename, Expression, UnicodeEncoding)
CATCH e AS Exception
THROW e
ENDTRY
Got the less verbose (and more abstract) syntax from X# documentation. Any reason why your suggestion must replace the argument-less THROW?
Once again, thank you for these and the other remarks. They surely are giving me a better understanding of X#.
StrToFile ( cExpression, cFileName [, lAdditive | nFlag])
Posted: Sun May 31, 2020 8:27 am
by Karl-Heinz
Hi Antonio,
>> DEFINE vs. #DEFINE
when you look at e.g. the FCreate() help notes, you´ll see that the second param supports constants like FC_READONLY, which is defined in the
https://github.com/X-Sharp/XSharpPublic ... le.prg#L85 as:
This is translated to a public const FC_READONLY. Such a constant is publicly visible, while e.g. your #DEFINE S2F_FLAG_UTF8 is only visible in the files in which the #DEFINE S2F_FLAG_UTF8 is included.
Wouldn't it be more appropriate to have these error messages centralized somewhere?
As I already mentioned earlier, first of all you need to know *exactly* what VFP does when invalid params are used or file operations fail. What about the SET SAFETY check if the file to create already exists ?
Got the less verbose (and more abstract) syntax from X# documentation. Any reason why your suggestion must replace the argument-less THROW?
If an error happens i want to know what went wrong, and such information is stored in the "CATCH e" exception object. Whether the exception is forwarded with "THROW e" or an errorcode is set instead depends on your requirements.
regards
Karl-Heinz
StrToFile ( cExpression, cFileName [, lAdditive | nFlag])
Posted: Sun May 31, 2020 5:32 pm
by atlopes
Karl-Heinz, thank you again.
Such a constant is publicly visible, while e.g. your #DEFINE S2F_FLAG_UTF8 is only visible in the files in which the #DEFINE S2F_FLAG_UTF8 is included.
Got it. Really important to know.
Wouldn't it be more appropriate to have these error messages centralized somewhere?
As I already mentioned earlier, first of all you need to know *exactly* what VFP does when invalid params are used or file operations fail.
I think we're addressing different issues, here. What I said was that I wouldn't feel too comfortable to have error messages as literal strings spreading around the code. That would not be beneficial to localization and style consistency. So, THROW SomeException {} instead of THROW SomeException { "Error message" }.
As for the need to replicate as much as possible the VFP behavior, I totally agree with you.
If an error happens i want to know what went wrong, and such information is stored in the "CATCH e" exception object.
As per the help file,
Using THROW within a CATCH block without any arguments re-throws the exception, passing it unchanged to the next highest TRY-CATCH block.
Am I wrongly interpreting this as setting the exception is redundant? Or does this have any side-effect that I'm missing?