Welcome, Guest
Username: Password: Remember me
This public forum is meant for questions and discussions about Visual FoxPro
  • Page:
  • 1
  • 2

TOPIC:

StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 01 May 2020 23:25 #14337

  • Karl-Heinz
  • Karl-Heinz's Avatar
  • Topic Author


  • Posts: 774
  • Guys,

    my FP-DOS doesn´t know the StrToFile() function, but according the VFP help the StrToFile() requirements are:

    1. text can be added, no matter if the file is currently in use.
    2. Text encoded as Unicode or utf8 can not be added to an existing file.
    3. if unicode or utf8 encoding is used the first bytes of the created file must be:
    - 'FF FE' (unicode)
    - 'EF BB BF' ( UTF8)

    otherwise the default ansi encoding is used.

    I would like to know if my current implementation gives the same results as the VFP StrToFile() ? i would also like to know which kind of errors VFP throws, e.g. if a invalid filename or a invalid nFlag param value is used.

    Note: In the start() function the cPath var currently points to a "D:\test\" dir, and the files to be examined are:

    "unicode.txt"
    "utf8.txt"
    "Ansi.txt"

    regards
    Karl-Heinz

    USING System.Text   
    USING System.IO
    
    FUNCTION Start() AS VOID 
    LOCAL cPath, cFile1, cFile2, cFile3 AS STRING 
       
    	cPath := "D:\test\"	
    	
    	cFile1 := cPath + "unicode.txt"
    	cFile2 := cPath + "utf8.txt"
    	cFile3 := cPath + "Ansi.txt"		
    		
    	? StrToFile( "Content of unicode file starts with: 'FF FE' " + Time() , cFile1 , 2 ) 
    	? StrToFile( "Append Text1" , cFile1 , 3 ) // This not allowed !
    	? StrToFile( "Append Text2" , cFile1 , 5 ) // This not allowed !
    	?
    	
    	? StrToFile( "Content of utf-8 file starts with: 'EF BB BF' " + Time() , cFile2 , 4 ) 
    	? StrToFile( "Append Text1" , cFile2 , 3 )  // This not allowed !
    	? StrToFile( "Append Text2" , cFile2 , 5 )  // This not allowed !
    	?
        
    	? StrToFile( "Text Ansi codepage " + Time() , cFile3 ) 
    	? StrToFile( "Append Text1" , cFile3 , TRUE ) 
    	? StrToFile( "Append Text2" , cFile3 , 1 ) 
    	? StrToFile( "Append Text3" , cFile3 , 1 ) 
    	?	
    
    	RETURN 
    
    FUNCTION StrToFile(cExpression, cFileName ) AS DWORD 
    RETURN	StrToFile(cExpression, cFileName , 0 )  
    
    FUNCTION StrToFile(cExpression AS STRING, cFileName AS STRING , lAdditive AS LOGIC) AS DWORD 
    RETURN	StrToFile(cExpression, cFileName  , IIF ( lAdditive , 1 , 0 ) )  
    
    FUNCTION StrToFile(cExpression AS STRING, cFileName AS STRING , nFlag AS DWORD ) AS DWORD 
    LOCAL enc AS System.Text.Encoding
    LOCAL lAdditive AS LOGIC
    LOCAL dwWritten AS DWORD
     
        	
    	IF nFlag == 3 .OR. nFlag > 4 
    		
    		// allowed flags are 0,1,2 or 4
    		// 
      		//  if the param nFlag value is not valid 
    		// What does VFP do ?  
    		
    		RETURN 0 
    
    	ENDIF		
    
    	
    	DO CASE 
    	CASE nFlag == 0 .OR. nFlag == 1 
    		
    		enc := RuntimeState.WinEncoding 
    		
    		IF nFlag == 1
    			lAdditive := TRUE 
    		ENDIF				
    		
    	CASE nFlag == 2
    		
    		// ensures that the first two bytes of the file  
    		// are "FF FE"
    		enc := System.Text.Encoding.Unicode
    		
    	CASE nFlag == 4
    		
    		// ensures that the first three bytes of the file  
    		// are "EF BB BF"
    		enc := System.Text.Encoding.UTF8
    		
    	ENDCASE  
    	
    
    	// The same can be done using a FileStream and a StreamWriter,
    	// instead of File.AppendAllText() and File.WriteAllText()
    	// Is there any reason to do it not the way i did ? 
    	
    	IF lAdditive
    		// If the file doesn´t exist it´s automatically created. 
    		File.AppendAllText(  cFileName, cExpression + Environment.NewLine, enc )
    		
    		dwWritten := (DWORD) cExpression:Length	
    	
    	ELSE 
    
    		// This depends on the SET SAFETY setting 
    		IF File.Exists( cFileName ) 
    			
    			// File already exists, ask the user to overwrite the file.
    			// What does VFP do ?	
    		
    			//	RETURN 0  			
    				
    		ENDIF		
    		
    		// if the file already exists, it will be overwritten.	
    		File.WriteAllText(cFileName  , cExpression + Environment.NewLine , enc ) 
    		
    		dwWritten := (DWORD) cExpression:Length	
    			
    	ENDIF 
    	
    	
    	RETURN dwWritten
    

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

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 02 May 2020 15:35 #14339

    • robert
    • robert's Avatar


  • Posts: 3600
  • Karl-Heinz,

    Your example code does not handle shared access I think. I don't think File.AppendAllText() allows the file to be in use by someone else (but that should not be too difficult to test)
    And the FoxPro help does not specify what happens when you include the Append flag and the Unicode or UTF8 flags, but the original files does not start with one of the Byte Order Marks. Should this lead to a failure or not ?
    Of the file starts with a Unicode flag but you are appending UTF8 or vice versa. Should that lead to a failure ?

    Robert

    Robert
    XSharp Development Team
    The Netherlands

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

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 02 May 2020 15:58 #14342

    • atlopes
    • atlopes's Avatar


  • Posts: 84
  • Karl-Heinz and Robert,

    Documented in the help file:

    When a BOM flag is specified, the file is always overwritten.

    The contents of the string must be assumed binary and must not be changed at all by the writing process. The STRTOFILE() may be used to write an image file to disk, for instance.

    The only intervention that the function introduces is the UNICODE prefix, but the string contents must already be encoded by the application if an encoding is required.

    Invalid flag numbers raise an error. That includes values 3 or 5.

    Not documented, but that you may find useful to know:

    In case the path to the file does not exist, the function returns 0.

    The number of bytes returned includes the BOM.


    All of this requires extra care, of course, since VFP text is ANSI based and that is about to change. But STRTOFILE() should really deal with its character data as it was binary.

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

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 02 May 2020 18:39 #14343

    • Karl-Heinz
    • Karl-Heinz's Avatar
    • Topic Author


  • Posts: 774
  • Hi Robert,

    my current implementation takes care about the various nFlag values. It´s not possible to append text to a already existing file if the nFlag values 3 (UNICODE + 1 ) or 5 ( UTF-8 + 1 ) are given. About the shared access: When i keep a already created file opened with Notepad, and create the same file again, neither File.AppendAllText() nor File.WriteAllText() throws an exception. There are also no access problems when i use a filestream + StreamWriter instead.

    In the meantime i´ve noticed that the MessageBox() function is already part of the VFP dll ;-). So the question exposed to the user: "overwrite a existing file or not", can be implemented in this way:
    IF File.Exists( cFileName ) // .AND. SetSafety() <-- doesn´t exist yet
    			
    	// File already exists - ask the user to overwrite the file. 
    		
    	IF XSharp.VFP.Functions.MessageBox ( "File " + cFileName +" already exits." + Environment.NewLine + ;
    			"Should the file be overwritten ?" , MB_YESNO  + MB_ICONQUESTION ) != IDYES
    					
    		RETURN 0  			
    	ENDIF 	
    				
    ENDIF	

    This requires a SetSafety () functionality such as:
    		//  VFP Dll
    				
    		PUBLIC STATIC METHOD SetSafety() AS LOGIC
    		RETURN RuntimeState.GetValue<LOGIC>(@@Set.Safety)
    
    		PUBLIC STATIC METHOD SetSafety(lSet AS LOGIC ) AS LOGIC
    		RETURN RuntimeState.SetValue(@@Set.Safety, lSet)
    		
    		
    		// part of a vh file
     
    		#command  SET SAFETY <x:ON,OFF,&>    =>  SetSafety( Upper(<(x)>)=="ON")
    		#command  SET SAFETY (<x>)           =>  SetSafety(  <x> )
    
    		

    regards
    Karl-Heinz

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

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 02 May 2020 18:55 #14344

    • Karl-Heinz
    • Karl-Heinz's Avatar
    • Topic Author


  • Posts: 774
  • Hi atlopes,

    thanks for your input, but the additional possibility to store e.g. a image via StrToFile() is new to me :woohoo: - at least the VFP StrToFile() docs don´t mention anything other than text files ... But, to solve problems step by step ;-): Would you be so kind to compare the content of the three txt files created by X#, with the content of the same files created by VFP ? Simply run the Start() code in your VFP environment.


    Invalid flag numbers raise an error. That includes values 3 or 5.

    Which error text excatly is thrown ?

    In case the path to the file does not exist, the function returns 0.

    ok

    The number of bytes returned includes the BOM.

    ok, that means that i must use filestream and StreamWriter, instead of File.AppendAllText() and File.WriteAllText()

    regards
    Karl-Heinz

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

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 02 May 2020 19:54 #14345

    • atlopes
    • atlopes's Avatar


  • Posts: 84
  • Karl-Heinz

    thanks for your input, but the additional possibility to store e.g. a image via StrToFile() is new to me :woohoo: - at least the VFP StrToFile() docs don´t mention anything other than text files ...


    In fact, VFP docs don't mention text files, either. The function just saves the contents of the string, whatever that might be, to a file.

    Would you be so kind to compare the content of the three txt files created by X#, with the content of the same files created by VFP ? Simply run the Start() code in your VFP environment.


    I gladly did.

    Contents differ in UNICODE and UTF-8 versions because you're changing the contents of the file as it's being written, due to the encoding process. The content of the string must be saved verbatim (I did change the value of the UTF-8 string expression just to force the need for an escaped sequence).

    Which error text excatly is thrown ?


    Error 11, "Function argument type, value, or count is invalid."

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

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 03 May 2020 00:17 #14346

    • Karl-Heinz
    • Karl-Heinz's Avatar
    • Topic Author


  • Posts: 774
  • seems i missunderstood the func.

    I hand over the function to you and go back to my VO ;-)

    regards
    Karl-Heinz

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

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 25 May 2020 01:34 #14700

    • atlopes
    • atlopes's Avatar


  • Posts: 84
  • Karl-Heinz

    The functions below are following the VFP behavior of STRTOFILE() and FILETOSTR(), I think. Like in VFP, the string must be previously encoded, if it's a Unicode text file.

    For instance
    STRTOFILE("Bouvard et Pécuchet", "t1.txt", 0)
    STRTOFILE(e"Bouvard et P\xC3\xA9cuchet", "t2.txt", 4)

    The STRTOFILE() overloads can remain as you suggested, but the Flags open range gives the opportunity of extending the functionality of the functions, like Unicode encoding.
    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
    
        DO CASE
            CASE Flags = 1
                Additive = .T.
                
            CASE Flags = 2
                BOM = e"\xFF\xFE"
                
            CASE Flags = 4
                BOM = e"\xEF\xBB\xBF"
                
            CASE Flags != 0
                RETURN 0
        ENDCASE
    
        IF Additive
    
            FHandle = FOpen(Filename, FO_READWRITE + FO_SHARED)
            IF FHandle != F_ERROR
                FSeek3(FHandle, 0, FS_END)
                Result = FWrite(FHandle, Expression)
                FClose(FHandle)
            ENDIF
    
        ELSE
            
            FHandle = FCreate(Filename)
            IF FHandle != F_ERROR
                IF ! (BOM == "")
                    Result = FWrite(FHandle, BOM)
                ENDIF
                Result += FWrite(FHandle, Expression)
                FClose(FHandle)
            ENDIF
    
        ENDIF
    
        RETURN Result
    
    ENDFUNC
    
    FUNCTION FileToStr (Filename AS String) AS String
        
        LOCAL FHandle AS Int
        LOCAL StrLen AS Int
        LOCAL Result = .NULL. AS String
        
        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
        
        RETURN Result
        
    ENDFUNC


    A question more (for the X# team, actually), regarding these functions but also others like this: are there any guidelines for exception handling?

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

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 25 May 2020 08:26 #14701

    • Chris
    • Chris's Avatar


  • Posts: 3982
  • Hi Antonio,

    In general, I'd say the behavior of the functions in X# should be the same as this of in VFP: If a function in VFP throws an error under some situations, so it should do for the same situations in X# as well. But if in other cases the VFP version swallows some errors, for example in invalide parameters etc and simply say returns FALSE or zero etc, so it should do also in the X# version, so programs ported to X# will continue having the same behavior as they used to.
    XSharp Development Team
    chris(at)xsharp.eu

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

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 25 May 2020 09:02 #14702

    • robert
    • robert's Avatar


  • Posts: 3600
  • Guys

    What Chris said.
    So I suggest:
    - when you expect a failure (like anything that reads/writes files), add a TRY CATCH
    - the low level file functions (FRead, FWrite etc) are already setting FError() and FException() so there is no need to duplicate that
    - Check the return values of these functions and abort the processing when there is an error.
    - When parameters are optional or can have different types with different meanings, keep that in X# as well.
    STRTOFILE(cExpression, cFileName [, lAdditive | nFlag])
    This can be implemented as:
    - either 3 separate functions (one with 2 params, one where the 3rd is logical and one where the 3rd is numeric)
    - or create one function where the 3rd is USUAL and has a default value
    - In this case I would probably create 3 functions and let 2 of them call the 3rd with the numeric 3rd parameter

    Btw I did not check it, but are you sure that StrToFile() creates what you want ?
    The FWrite() function translates the characters in the string to Bytes with the current Windows Encodings.
    So you may be writing a byte order mark correctly, but the result in the file will still be Ansi I think.
    To write Unicode text I would recommend to use something like System.IO.File.WriteAllText() with a unicode encoding or System.IO.File.AppendAllText() if you want to append to an existing file. And if you use these with a unicode encoding then these methods will take case of the Byte Order Mark automatically.

    Robert
    XSharp Development Team
    The Netherlands

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

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 25 May 2020 13:37 #14706

    • Karl-Heinz
    • Karl-Heinz's Avatar
    • Topic Author


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

    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

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

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 25 May 2020 19:13 #14709

    • atlopes
    • atlopes's Avatar


  • Posts: 84
  • 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.
    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.

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

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 25 May 2020 22:53 #14714

    • robert
    • robert's Avatar


  • Posts: 3600
  • 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.
    docs.microsoft.com/en-us/dotnet/api/system.io.file.readallbytes
    docs.microsoft.com/en-us/dotnet/api/syst...o.file.writeallbytes

    Robert
    XSharp Development Team
    The Netherlands

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

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 26 May 2020 00:31 #14717

    • atlopes
    • atlopes's Avatar


  • Posts: 84
  • 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.

    www.xsharp.eu/runtimehelp/html/M_XSharp_...Functions_FWrite.htm

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

    Last edit: by atlopes.

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 26 May 2020 08:43 #14719

    • robert
    • robert's Avatar


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

    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
    XSharp Development Team
    The Netherlands

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

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 26 May 2020 19:29 #14724

    • atlopes
    • atlopes's Avatar


  • Posts: 84
  • 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.
    * 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"\xFF\xFE"
    
            CASE Flags = S2F_FLAG_UTF8
                BOM = e"\xEF\xBB\xBF"
    
            CASE Flags = S2F_FLAG_UNICODE_BE
                BOM = e"\xFE\xFF"
                
            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

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

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 27 May 2020 21:49 #14742

    • Karl-Heinz
    • Karl-Heinz's Avatar
    • Topic Author


  • Posts: 774
  • Hi Antonio,

    i think your defines should be declared in this way
    * 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:
    THROW ArgumentException { String.Format("Param value {0} is invalid" , Flags ) , NAMEOF ( Flags ) }
    and
    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:
    ? 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.
    ...
           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

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

    Last edit: by Karl-Heinz.

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 30 May 2020 14:14 #14768

    • atlopes
    • atlopes's Avatar


  • Posts: 84
  • Karl-Heinz, thank you for your remarks.

    i think your defines should be declared in this way

    * 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:

    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

    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#.

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

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 31 May 2020 10:27 #14780

    • Karl-Heinz
    • Karl-Heinz's Avatar
    • Topic Author


  • Posts: 774
  • 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 github.com/X-Sharp/XSharpPublic/blob/37e...nctions/File.prg#L85 as:
    DEFINE FC_READONLY   := 0x00000001  
    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

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

    StrToFile ( cExpression, cFileName [, lAdditive | nFlag]) 31 May 2020 19:32 #14786

    • atlopes
    • atlopes's Avatar


  • Posts: 84
  • 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?

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

    • Page:
    • 1
    • 2