A comma-separated values (CSV) file stores tabular data (numbers and text) in plain-text form. Each column in the file is, as the name suggests, separated by commas. JACL supports both the reading and writing of CSV files. By default, CSV files are stored in a directory named data in the same location as the program that is accessing them. For example, the demonstration program contacts.jacl lives in the projects directory and stores its data in projects/data. When referring to a CSV file using any of the commands detailed in this section, there are two ways to specify the filename:
Per-game files — supply a short name without a file extension. This name is used to build the full filename using the following formula:
<DataDirectory>/<ProgramName>-<Name>.csv
For example, the program contacts.jacl stores its contacts in a CSV file called data which translates to the full file:
projects/data/contacts-data.csv
This system ensures that no two JACL programs can overwrite each other's data or access arbitrary files from the filesystem of the webserver.
Global files — supply a filename that includes the .csv extension, enclosed in double quotes. The file is accessed directly from the data directory without any program name prefix:
<DataDirectory>/<Name>
For example, a shared vocabulary file called indonesian_words.csv can be accessed by any program:
iterate "indonesian_words.csv" skip_header write field[0] ": " field[1] ^ enditerate
The quotes are required to prevent the dot in the filename from being interpreted as a scope separator.
This is useful for reference data that is shared across multiple programs.
Any ITERATE command must be of the following format:
iterate <FileName> [skip_header] ... enditerate
The iterate and enditerate commands mark a looping block of code. The block of code is run once for each row in the file specified directly after the iterate command. For example, the +intro function of the program contacts.jacl loops through the file data and looks for the last (highest) ID already assigned to a contact. This number is stored so that the next contact created can be given an ID that one higher, and therefore unique:
# GET THE LAST ID
iterate data
if field[0] < last_id
set last_id = field[0]
endif
enditerate
Each column of the current row being read is loaded into a string array called field. In the case of contacts.jacl, the ID is stored in the first column, or field.
Below is another example of the iterate command, this time it is used to display the entire contents of the file data:
iterate data skip_header
set INDEX = 0
while INDEX < field_count
write field[INDEX]
write ", "
set INDEX + 1
endwhile
write "^"
enditerate
There are two points of note with this second example. The first is that the optional flag skip_header has been specified after the file name. This flag tells the iterate command to ignore the first line of the file as it contains column headers, not data. For example, a CSV such as this:
ID First name Last name 0 Stuart Allen 1 Fred Fargnargle 2 Zaphod Beeblebrox
The second is the constant field_count. This constant contains the number of columns read for the current row. JACL supports a maximum of twenty columns for any given row.
Any update command must be of the following format:
update <FileName> ... endupdate
Any insert command must be of the following format:
insert <Field0> [<Field1>...]
The update is used to re-write a CSV and therefore provide the opportunity to edit the contents. Below is an example of its use from the +update function of contacts.jacl
update data
if field[0] = $integer
insert field[0] firstname_data surname_data email_data home_phone_data mobile_phone_data address_data
else
insert field[0] field[1] field[2] field[3] field[4] field[5] field[6]
endif
endupdate
write "Record " $integer " updated.^^"
This function uses the update command to modify the details for the contact with the ID (field[0]) that is equal to $integer. This is achieved by looping through each line of the file (using the update command) and then checking if the current line is the line that is to be edited. If so, the new values are written out (using the insert command). If not, the existing values are written back to the file (also using the insert command.)
The update and insert commands are also used to delete a record in contacts.jacl. This is achieved by inserting all the existing rows from within an update loop except the record to be deleted:
update data
if field[0] != $integer
insert field[0] field[1] field[2] field[3] field[4] field[5] field[6]
endif
endupdate
write "Record " $integer " deleted.^^"
Any append command must be of the following format:
append <FileName> <Field0> [<Field1>...]
The append command is used to write a single extra row to the end of a CSV file. If the CSV file doesn't already exist it will be created and the appended row written to the new file. Below is an example of its use from the +add_record function of contacts.jacl:
set last_id + 1 append data last_id firstname_data surname_data email_data home_phone_data mobile_phone_data address_data write firstname_data " " surname_data " added to database.^^"
These three commands are used together to build a single CSV row incrementally across multiple commands. This is useful when the number of columns in a row is not known in advance, such as when iterating over a variable-length data set.
The commands use the following format:
append_fc <FileName> <Field0> [<Field1>...] append_nt <FileName> <Field0> [<Field1>...] append_lc <FileName> <Field0> [<Field1>...]
The append_fc (first column) command starts a new line in the CSV file and writes the specified fields. It does not terminate the line, leaving it open for further fields to be added.
The append_nt (no terminate) command continues writing fields to the current open line without terminating it. This command can be called multiple times to add additional columns.
The append_lc (last column) command continues writing fields to the current open line and then terminates the line with a newline character, closing the file.
Below is an example of these three commands being used together to build a row with a variable number of columns:
append_fc results date time name
iterate data skip_header
if COUNT < obs_count
append_nt results observations[INDEX]
set COUNT + 1
else
append_lc results observations[INDEX]
endif
enditerate
In this example, append_fc starts the row with three fixed columns. The iterate loop then adds a variable number of observation columns using append_nt, with the final column being written using append_lc to close the line.