StrToFile ( cExpression, cFileName [, lAdditive | nFlag])

This forum is meant for questions about the Visual FoxPro Language support in X#.

Karl-Heinz
Posts: 774
Joined: Wed May 17, 2017 8:50 am

StrToFile ( cExpression, cFileName [, lAdditive | nFlag])

Post by Karl-Heinz »

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

Code: Select all

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

User avatar
robert
Posts: 4225
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

StrToFile ( cExpression, cFileName [, lAdditive | nFlag])

Post by robert »

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
robert@xsharp.eu
atlopes
Posts: 83
Joined: Sat Sep 07, 2019 11:43 am

StrToFile ( cExpression, cFileName [, lAdditive | nFlag])

Post by atlopes »

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.
Karl-Heinz
Posts: 774
Joined: Wed May 17, 2017 8:50 am

StrToFile ( cExpression, cFileName [, lAdditive | nFlag])

Post by Karl-Heinz »

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:

Code: Select all

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:

Code: Select all

		//  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
Karl-Heinz
Posts: 774
Joined: Wed May 17, 2017 8:50 am

StrToFile ( cExpression, cFileName [, lAdditive | nFlag])

Post by Karl-Heinz »

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
atlopes
Posts: 83
Joined: Sat Sep 07, 2019 11:43 am

StrToFile ( cExpression, cFileName [, lAdditive | nFlag])

Post by atlopes »

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."
Karl-Heinz
Posts: 774
Joined: Wed May 17, 2017 8:50 am

StrToFile ( cExpression, cFileName [, lAdditive | nFlag])

Post by Karl-Heinz »

seems i missunderstood the func.

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

regards
Karl-Heinz
atlopes
Posts: 83
Joined: Sat Sep 07, 2019 11:43 am

StrToFile ( cExpression, cFileName [, lAdditive | nFlag])

Post by atlopes »

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

Code: Select all

STRTOFILE("Bouvard et Pécuchet", "t1.txt", 0)
STRTOFILE(e"Bouvard et PxC3xA9cuchet", "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.

Code: Select all

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"xFFxFE"
            
        CASE Flags = 4
            BOM = e"xEFxBBxBF"
            
        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?
User avatar
Chris
Posts: 4562
Joined: Thu Oct 08, 2015 7:48 am
Location: Greece

StrToFile ( cExpression, cFileName [, lAdditive | nFlag])

Post by Chris »

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.
Chris Pyrgas

XSharp Development Team test
chris(at)xsharp.eu
User avatar
robert
Posts: 4225
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

StrToFile ( cExpression, cFileName [, lAdditive | nFlag])

Post by robert »

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
robert@xsharp.eu
Post Reply