Skip to content

File I/O Advanced

This program is a more complicated version of the basic File I/O tutorial (here) and will involve string manipulation (tutorial here) as well as time (tutorial here). We are going to create a program which takes a file as an input, gets the individuals lines out of that string, extracts times from these lines, and then outputs a total to a file.

Setting Up

After you have created this program we need to do several things, first, we need to add the log-handler file to the project. To do this you need to copy log-handler.sma into the folder which has the same name as your project within the project folder in my case \AdvancedIO\AdvancedIO this will allow you to use the file in your program. The next thing you need to do is add the following libraries to your project in Project Settings

We are going to come back to the Project Settings later on but for now let’s declare the variables we will need

Variable Declaration

constant sTIMEFORMAT "0h:mm:ss"

function main()

  string sInputfilename
  string sOutputfilename;   
  integer iErrorno;
  string sLogfile;
  string sLine;
  string sDay
  time tSubTotal;
  time tTotal;

  LogHandler log_handler;
  fsfileinputstream inputfile;
  fsfileoutputstream outputfile;

These are mostly self-explanatory, the first two variables are for input and output file names, iErrorno is used to deal with errors in opening these files, sLogfile is going to be the name we give to our log file. sLine and sDay are both used within our string manipulation one as the line we get from the file, the other as a placeholder in our string logic. tSubTotal and tTotal are temporary variables we will use as output. Finally log_handler ,inputfile,outputfile are all objects we need to declare and will act as our log handler, input, and output.

N.B. The naming scheme I am using is for visibility, any non-object will be prefaced with the first letter of its type, you can, of course, use whatever names you want

Variable Initialisation

To do anything useful we will need to initialise the variables we have just created.

  sInputfilename = "examples.txt"
  sOutputfilename = "AdvancedIOOutput.txt"
  sLogfile = "AdvancedIO.log";
  iErrorno = 0
  sDay = ""
  tSubTotal =@ time.new()
  tTotal =@ time.new()
  tSubTotal.set(0,0,0,0,0);
  tTotal.set(0,0,0,0,0);

This part is mostly self-explanatory with the exception of the time variables, to use this properly requires object like initialisation and then setting their value.

The object initialisation should also be self-explanatory at this point, however, I want to point out that there is no need to add the error variable, and we will not be covering error handling in this tutorial. If however, you want to do error handling that should go directly after the initialisation. Not including the variable means that if the program runs into an issue it will stop execution and output an error code, as it is in this program execution will continue but will not work as intended.

  log_handler =@ LogHandler.new(sLogfile);
  inputfile =@ fsfileinputstream.new(sInputfilename,iErrorno);
  outputfile =@ fsfileoutputstream.new(sOutputfilename,iErrorno);

Loop

Once we have declared all our variables we can start our loop. The first thing to do is to read the file line by line:

while inputfile.endofdata != .true
    sLine = inputfile.getstring(5000,1,terminator="{d}{a}");

This code should look familiar to you if you have completed the File I/O tutorial. It will get the first 5000 characters or until it finds a newline terminator. This next part will vary from use case to use case, in our case the text file has the following layout:

W30 2018-07-25 Wed @7  SIMPOL: Bug fixes                                                          5:15:56  6:57:40 1:41:44
                   @6  Accounting, budgeting                                                     16:18:51 17:13:36 0:54:45
                   @5  SIMPOL: Bug fixes                                                         17:13:41 18:27:53 1:14:12
                   @4  SIMPOL: Bug fixes                                                         21:11:26 21:56:06 0:44:40  4:35:21

In this example we want to do the following total all the SIMPOL times for each day, to do this we will need to separate each day of the week and then total all the times that contain the keyword we are looking for. To separate the days of the week we use the following if statement:

if .instr(sLine,"Mon") != 0 or (sDay == "Mon" and .lstr(sLine,1) !="W")

This statement is relatively complex, to return true it requires one of the following to be true: in the line, we find the string “Mon” (for Monday), or our temporary variable is equal to monday and the first character in the left of the string is not equal to “W”. This second condition is true whenever we are trying to total times within a day, and have not reached a new day.

If we are starting a new day we need to do several things: output the sub-total of the last day, if and only if this is not the first day, add the sub-total to the overall total, select the current day (set the temporary variable) and reset the tSubTotal to zero.

    if sDay != "Mon"
        if sDay != ""
          outputfile.putstring(sDay + " subtotal: " + .tostr(tSubTotal.hours(),10) + ":" + .tostr(tSubTotal.minutes(),10) + ":" + .tostr(tSubTotal.seconds(),10)+ "{d}{a}",1);
        end if
        tTotal = tTotal + tSubTotal 
        sDay = "Mon"
        tSubTotal.set(0,0,0,0,0)
    end if 

Function 1

The following piece of code will be repeated often it is thus best to put it into a function. This function will extract the start and end time from the line and output the difference between the two. This function looks something like this:

function getTime(string sLine,time tTotal)
     if .instr(sLine, "SIMPOL:") != 0 and sLine != .nul;
         string temp;
         string sStartTime;
         string sEndTime;
         time tStartTime;
         time tEndTime;

         tEndTime = time.new();
         tStartTime = time.new();

         sLine = .rstr(sLine, 35)
         sLine = rtrim(sLine)
         sLine = ltrim(sLine)

         temp = .lstr(sLine, 17)
         temp = rtrim(temp)

         sStartTime = .lstr(temp,8)
         sStartTime = rtrim(sStartTime)

         sEndTime = .rstr(temp,8)
         sEndTime = ltrim(sEndTime)

         tStartTime = string2time(sStartTime,sTIMEFORMAT);
         tEndTime = string2time(sEndTime,sTIMEFORMAT);
         tTotal = tTotal + (tEndTime - tStartTime);
    end if
end function tTotal

This function may look complicated, but it’s not really, the first thing we do is test if in the line we pass into the function we can find our matching phrase, in our case "SIMPOL:", we also check that the line is not a null object. Once this check has run we can declare our variables and initialise our variables

The next thing we do is to get the first 35 characters on the right of the line, why 35 characters exactly? Because in our case that is the longest a set of times can be. We then want to trim all spaces from the left and right. The spaces on the left have the added complication that if the description is long enough characters will appear on the very left and interfere with our numbers if there were a pattern to this interference we could then add that pattern to the sCharList variable in ltrim.

Now we can start separating the start and end times, this is done by taking 17 characters from the left and then trimming it again. Now we have something containing both the start and end times, we need to split this up by taking 8 characters from either side and making these separate strings. We then use string2time to convert our start and end time to proper times. The final thing we need to do is to get an output. In our case, we will add the total time we got as an input to the difference between the start and end time we’ve just formatted.

Copy-Paste

Once this has been done we now need to copy and paste the daily subtotal code we made before this function and change our sDay variable to different days: “Mon”, “Tue”, “Wed”, “Thu”, “Fri”, “Sat”, “Sun”. The code should now look almost identical to the completed code (below). The last thing to do now is to output the sum total to the file:

outputfile.putstring("Total: " + .tostr(tTotal.hours(.true),10) + ":" + .tostr(tTotal.minutes(),10) +  ":" + \
                     .tostr(tTotal.seconds(),10) + "{d}{a}",1);

N.B. There is in the current version of time a bug where you need to set the variable in hours to .true to get accurate times otherwise it will reset every 24 hours

Additional Functionality

This program will now work perfectly well however, it is not amazingly easy to use especially if we are wanting to change the file often, to fix this we are going to add a command-line target to change the input file, this is quite easy to do we need to add an input into our main function, similar to any other function. The main function should now start like this:

function main(string inputfilename)

This will then allow you to in project settings set a command line parameter, similarly, this will allow you to launch the program using the command line and set the input file.

This step is needed for the next part of this tutorial

Output

If you have done everything right you should get the following output from the exampe file

Tue subtotal: 3:14:25
Wed subtotal: 3:40:36
Thu subtotal: 4:56:55
Total: 11:51:56

Follow On

The next part of this tutorial will concern itself with the building of a GUI interface for this program and can be found (here)


Downloads & Further Learning

A more in depth look at string manipulation
A more in depth look at time in SIMPOL

Full Code

constant sTIMEFORMAT "0h:mm:ss"

function main(string sInputfilename)

  //Variables declaration
  string sOutputfilename;   
  integer iErrorno;
  string sLogfile;
  string sLine;
  string sDay
  time tSubTotal;
  time tTotal;

  //Object declaration
  LogHandler log_handler;
  fsfileinputstream inputfile;
  fsfileoutputstream outputfile;

  //Variable initalisation
  sOutputfilename = "AdvancedIOOutput.txt"
  sLogfile = "AdvancedIO.log";
  iErrorno = -1
  sDay = ""
  tSubTotal =@ time.new()
  tTotal =@ time.new()
  tSubTotal.set(0,0,0,0,0);
  tTotal.set(0,0,0,0,0);

  //Object initialisation
  log_handler =@ LogHandler.new(sLogfile);
  inputfile =@ fsfileinputstream.new(sInputfilename,iErrorno);
  outputfile =@ fsfileoutputstream.new(sOutputfilename,iErrorno);

  while inputfile.endofdata != .true
    sLine = inputfile.getstring(5000,1,terminator="{d}{a}");
    
    //Monday
    if .instr(sLine,"Mon") != 0 or (sDay == "Mon" and .lstr(sLine,1) !="W")
      if sDay != "Mon"
        if sDay != ""
          outputfile.putstring(sDay + " subtotal: " + .tostr(tSubTotal.hours(),10) + ":" + .tostr(tSubTotal.minutes(),10) + ":" + .tostr(tSubTotal.seconds(),10)+ "{d}{a}",1);
        end if 
        tTotal = tTotal + tSubTotal 
        sDay = "Mon"
        tSubTotal.set(0,0,0,0,0)
      end if
      tSubTotal = getTime(sLine,tSubTotal)

    //Tuesday
    else if .instr(sLine,"Tue") != 0 or (sDay == "Tue" and .lstr(sLine,1) !="W")
      if sDay != "Tue"
        if sDay != ""
          outputfile.putstring(sDay + " subtotal: " + .tostr(tSubTotal.hours(),10) + ":" + .tostr(tSubTotal.minutes(),10) + ":" + .tostr(tSubTotal.seconds(),10)+ "{d}{a}",1);
        end if 
        tTotal = tTotal + tSubTotal 
        sDay = "Tue"
        tSubTotal.set(0,0,0,0,0)
      end if
      tSubTotal = getTime(sLine,tSubTotal)

    //Wednesday
    else if .instr(sLine,"Wed") != 0 or (sDay == "Wed" and .lstr(sLine,1) !="W")
      if sDay != "Wed"
        if sDay != ""
          outputfile.putstring(sDay + " subtotal: " + .tostr(tSubTotal.hours(),10) + ":" + .tostr(tSubTotal.minutes(),10) + ":" + .tostr(tSubTotal.seconds(),10)+ "{d}{a}",1);
        end if 
        tTotal = tTotal + tSubTotal 
        sDay = "Wed"
        tSubTotal.set(0,0,0,0,0)
      end if
      tSubTotal = getTime(sLine,tSubTotal)

    //Thursday
    else if .instr(sLine,"Thu") != 0 or (sDay == "Thu" and .lstr(sLine,1) !="W")
      if sDay != "Thu"
        if sDay != ""
          outputfile.putstring(sDay + " subtotal: " + .tostr(tSubTotal.hours(),10) + ":" + .tostr(tSubTotal.minutes(),10) + ":" + .tostr(tSubTotal.seconds(),10)+ "{d}{a}",1);
        end if 
        tTotal = tTotal + tSubTotal 
        sDay = "Thu"
        tSubTotal.set(0,0,0,0,0)
      end if
      tSubTotal = getTime(sLine,tSubTotal)

    //Friday
    else if .instr(sLine,"Fri") != 0 or (sDay == "Fri" and .lstr(sLine,1) !="W")
      if sDay != "Fri"
        if sDay != ""
          outputfile.putstring(sDay + " subtotal: " + .tostr(tSubTotal.hours(),10) + ":" + .tostr(tSubTotal.minutes(),10) + ":" + .tostr(tSubTotal.seconds(),10)+ "{d}{a}",1);
        end if 
        tTotal = tTotal + tSubTotal 
        sDay = "Fri"
        tSubTotal.set(0,0,0,0,0)
      end if
      tSubTotal = getTime(sLine,tSubTotal)

    //Saturday
    else if .instr(sLine,"Sat") != 0 or (sDay == "Sat" and .lstr(sLine,1) !="W")
      if sDay != "Sat"
        if sDay != ""
          outputfile.putstring(sDay + " subtotal: " + .tostr(tSubTotal.hours(),10) + ":" + .tostr(tSubTotal.minutes(),10) + ":" + .tostr(tSubTotal.seconds(),10)+ "{d}{a}",1);
        end if 
        tTotal = tTotal + tSubTotal 
        sDay = "Sat"
        tSubTotal.set(0,0,0,0,0)
      end if
      tSubTotal = getTime(sLine,tSubTotal)

    //Sunday
    else if .instr(sLine,"Sun") != 0 or (sDay == "Sun" and .lstr(sLine,1) !="W")
      if sDay != "Sun"
        if sDay != ""
          outputfile.putstring(sDay + " subtotal: " + .tostr(tSubTotal.hours(),10) + ":" + .tostr(tSubTotal.minutes(),10) + ":" + .tostr(tSubTotal.seconds(),10)+ "{d}{a}",1);
        end if 
        tTotal = tTotal + tSubTotal 
        sDay = "Sun"
        tSubTotal.set(0,0,0,0,0)
      end if
      tSubTotal = getTime(sLine,tSubTotal)
    end if
  end while
outputfile.putstring("Total: " + .tostr(tTotal.hours(.true),10) + ":" + .tostr(tTotal.minutes(),10) +  ":" + .tostr(tTotal.seconds(),10) + "{d}{a}",1)    ;
end function "" 

function getTime(string sLine,time tTotal)
     if .instr(sLine, "SIMPOL:") != 0 and sLine != .nul;
         //Declaration of variables
         string temp;
         string sStartTime;
         string sEndTime;
         time tStartTime;
         time tEndTime;

         //Convert time strings to time objects (Need to initalise before can be used properly) (also set, if not being set elsewhere)
         tEndTime = time.new();
         tStartTime = time.new();

         //Get all times
         sLine = .rstr(sLine, 35)
         sLine = rtrim(sLine)
         sLine = ltrim(sLine,", simpol")

         //Get just start and end time
         temp = .lstr(sLine, 17)
         temp = rtrim(temp)

         //Get Start time
         sStartTime = .lstr(temp,8)
         sStartTime = rtrim(sStartTime)

         //Get End time
         sEndTime = .rstr(temp,8)
         sEndTime = ltrim(sEndTime)

         tStartTime = string2time(sStartTime,sTIMEFORMAT);
         tEndTime = string2time(sEndTime,sTIMEFORMAT);
         tTotal = tTotal + (tEndTime - tStartTime);
    end if
end function tTotal