Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Writing and reading IFS files #12

Open
worksofliam opened this issue Jul 7, 2019 · 3 comments
Open

Writing and reading IFS files #12

worksofliam opened this issue Jul 7, 2019 · 3 comments
Labels
ilerpg ILE RPG topics

Comments

@worksofliam
Copy link
Owner

This blog is a follow up from "A better way to read a file in the IFS with RPG" on RPGPGM.com. Writing to the IFS can be important. It can be really useful to store your own log files instead of writing to a physical file.. it may even be slightly easier. When I say file in this blog post, I mean IFS stream file - not physical file.

As a standard, I use this Template data structure for each IFS file I want to create or open in my file:

Dcl-Ds File_Temp Qualified Template;
  PathFile char(128);
  RtvData  char(256);
  OpenMode char(5);
  FilePtr  pointer inz;
End-ds;

I define PathFile as a Char(128) because I don't usually have paths that are more than 128 characters long. RtvData should be the longest length of a line that you will find in any stream file that you open. OpenMode requires a bit more knowlegde. There are different 'open codes' for different types of operations - such as reading, appending or overwrite. You can find a list of codes available on this MCPress article by Robert Cozzi (about half way down).

Next you will want the four prototypes I use for all my code, which we will talk more about later.

dcl-pr OpenFile pointer extproc('_C_IFS_fopen');
  *n pointer value;  //File name
  *n pointer value;  //File mode
end-pr;

dcl-pr ReadFile pointer extproc('_C_IFS_fgets');
  *n pointer value;  //Retrieved data
  *n int(10) value;  //Data size
  *n pointer value;  //Misc pointer
end-pr;

dcl-pr WriteFile pointer extproc('_C_IFS_fwrite');
  *n pointer value;  //Write data
  *n int(10) value;  //Data size
  *n int(10) value;  //Block size
  *n pointer value;  //Misc pointer
end-pr;

dcl-pr CloseFile extproc('_C_IFS_fclose');
  *n pointer value;  //Misc pointer
end-pr;

I use OpenMode = 'ab' + x'00' to overwrite (or create) files and I use OpenMode = 'r' + x'00' if I am purely reading a file. I am basing my code off two different sources file which are both open-source under the Relic Package Manager repository - they both use the data structure I declared at the top of this blog.

Reading a file

This section of code comes from RELIC.SQLRPGLE. The logic of this code is to open a 'build file' and read through it's content. I firstly need to declare a new data structure over the template we created earlier.

Dcl-Ds gBuildFile LikeDS(File_Temp);

Now we can store anything relevant to this specific file in this data structure, great! Next we need to put data into some of the sub-fields within our gBuildFile data structure.

//Process the build file
gBuildFile.PathFile = 'build.txt' + x'00';
gBuildFile.OpenMode = 'r' + x'00';
gBuildFile.FilePtr  = OpenFile(%addr(gBuildFile.PathFile)
                              :%addr(gBuildFile.OpenMode));

Now, you may be asking what x'00' is. As Simon says on his blog, he is null terminating the character fields. This basically tells the function where to stop reading. As a side note, if you don't explicitly specify the path of the file it will use your current directory. Like I mentioned earlier, I am using r for read mode on the file I have specified.

What if the file didn't open? What if it didn't exist or some something stupid has happened and you can't open the file? We'd need to handle that of course. This simple if statement checks if the pointer to the file is *null, and if it is then it's not working.

If (gBuildFile.FilePtr = *null);
  Print('Failed to read build file.');
  Return *Off;
EndIf;

So it would only get past this point if the file has been opened correctly, which is what we want. Next, we can loop through our file and read the contents of it - using a do while! Nice and simple. So while ReadFile returns a pointer, it also passes in the lines value into one of the parameters by reference since we're passing it a pointer and length (gBuildFile.RtvData)

Dow  (ReadFile(%addr(gBuildFile.RtvData)
              :%Len(gBuildFile.RtvData)
              :gBuildFile.FilePtr) <> *null);

  gBuildFile.RtvData = ' ';
Enddo;

Before the end of the do-loop, it is vital we assign blank to gBuildFile.RtvData. If you don't characters will stick around in there when you read the next line. Since files can also contain weird things like line breaks, end of records, tabs, etc, we are going to need to remove those so we don't have anything icky in our program! After the beginning of the do-loop, you'll want these %xlates to replace those weird bytes.

That's it. You can now read the clean data from your stream file in the IFS! The code might look something like this:

Dow  (ReadFile(%addr(gBuildFile.RtvData)
              :%Len(gBuildFile.RtvData)
              :gBuildFile.FilePtr) <> *null);
  gBuildFile.RtvData = %xlate(x'00':' ':gBuildFile.RtvData);//End of record null
  gBuildFile.RtvData = %xlate(x'25':' ':gBuildFile.RtvData);//Line feed (LF)
  gBuildFile.RtvData = %xlate(x'0D':' ':gBuildFile.RtvData);//Carriage return (CR)
  gBuildFile.RtvData = %xlate(x'05':' ':gBuildFile.RtvData);//Tab

  If (gBuildFile.RtvData = 'Hello!!');
    DSPLY 'Line in file says hi!';
  ENDIF;

  gBuildFile.RtvData = ' ';
Enddo;

Writing a file

Writing a file has the same concept as reading a file. You open, write and close. Like before, I am going to use my File_Temp data-structure template to store my data in. I am also going to use ab as my open mode so API will create or overwrite the file if it already exists.

Dcl-Ds gStreamFile LikeDS(File_Temp);

gStreamFile.PathFile = 'helloworld.txt';
gStreamFile.OpenMode = 'ab' + x'00';
gStreamFile.FilePtr  = OpenFile(%addr(gStreamFile.PathFile)
                               :%addr(gStreamFile.OpenMode));

Next is writing to that file, easy! Don't forget you must manually append line breaks (x'25') if you want those in your stream file.

Dcl-S pValue Char(100);

pValue = 'Hello world!' + x'25';

WriteFile(%Addr(pValue)
         :%Len(%TrimR(pValue))
         :1
         :gStreamFile.FilePtr);

So you can write as many times as you want, but do not forget to close the file!

CloseFile(gStreamFile.FilePtr);

And that's how you write to a file! Nice and simple, all within free-format RPG.

@worksofliam worksofliam added the ilerpg ILE RPG topics label Jul 7, 2019
@JonFP
Copy link

JonFP commented Dec 3, 2019


dcl-pr OpenFile pointer extproc('_C_IFS_fopen');
  *n pointer value;  //File name
  *n pointer value;  //File mode
end-pr;

The resulting calls will be both simpler and look "more RPG like" if Options(*String) is added to the proto parameters and if the Pathfile variable etc. are changed to varchar. In fact I would normally coder the mode directly as shown below. The current calls would still be valid but a simplified version would also be available. e.g.


`dcl-pr OpenFile pointer extproc('_C_IFS_fopen');
  *n pointer value options(*string);  //File name
  *n pointer value options(*string);  //File mode
end-pr;

gStreamFile.PathFile = 'helloworld.txt';

gBuildFile.FilePtr  = OpenFile( gBuildFile.PathFile )
                              :  'r' );

This option causes RPG to copy the supplied data to a temporary area and null terminate it for you. The result as I said is a far more RPG like call operation. It also calms the pointer-phobic!

@sankha007
Copy link

Hi, What if I want to write the contents of a PF(physical file) having 100 fields, how do I do that. As I should write the 100 fields exclusively in the program. Please help.

@JonFP
Copy link

JonFP commented Feb 13, 2022

Hi, What if I want to write the contents of a PF(physical file) having 100 fields, how do I do that. As I should write the 100 fields exclusively in the program. Please help.

You basically have a number of options.

  1. Use CPYTOIMPF which gives you a CSV file with the contents.
  2. Use a READ into a DS (externally described based on the file) and use the DS name as the source for the IFS write. This won't work well though if there are packed fields in the file. If there are you will need to build a logical over the file using zoned to redefine the packed fields and use that file as the data source.
  3. Use a regular read and use %Char %Edit etc. to string out the record buffer in the layout you want.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ilerpg ILE RPG topics
Projects
None yet
Development

No branches or pull requests

3 participants