WSU Tri-Cities CptS 121 (Spring, 2017)
Due Date:4/6/17

Homework 6: The Monster Database

The CSV Format

CSV ("comma-separated values") is a simple, very widely used format for tabular data. As the name implies, this is a file format where each line is a row of a table and each column is separated from the others by commas.

There are a number of variations on CSV that allow quite a bit of flexibility, but we'll keep it simple by assuming that our data itself does not contain commas or newlines, so we can always treat these as separators.

We will also adopt the common convention that the first line of a CSV file includes the names of the data attributes themselves. This "self-describing data" convention is a useful safeguard and your program will check the names against what it thinks the database structure is.

Part 1: Sorting a CSV File

The first part of this homework will have you write code to read a CSV file on standard input into a database (table of Monster structs) internal to the program, sort that table by year of release, and write a CSV file on standard output. The sorting part being a little tricky, it's been done for you. All you have to provide are the routines to read and write the database.

Here are the steps:

  1. Download the template monster_sortdb_tplt.c from the course web link for this homework:

    http://www.tricity.wsu.edu/~bobl/cpts121/hw06_monsterdb

  2. Rename it to monster_sortdb.c:

    $ mv monster_sortdb_tplt.c monster_sortdb.c
    

    Here are its contents:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/*
 * Copy the `struct Monster` definition here.
 */

/*
 * Declaring storage for the database.
 */
#define MAX_MONSTERS 1024 /* should be big enough */
struct Monster monsters[MAX_MONSTERS];

void printCsvHeader(void)
/* print a CSV-compatible "monster" header to standard output */
{
    /*
     * ASSIGNMENT
     *
     * print each attribute name of the monster in order, followed by
     *  a ",", except for the last one, which should be followed by a
     *  newline ("\n").
     */
}

void printCsvRow(struct Monster monster)
/* print a CSV-compatible "monster" row to standard output */
{
    /*
     * ASSIGNMENT
     *
     * print each attribute of the monster in order, followed by a
     *  ",", except for the last one, which should be followed by a
     *  newline ('\n').
     */
}

void saveDatabase(struct Monster monsters[])
{
    /*
     * ASSIGNMENT
     *
     * print the CSV header with printCsvHeader()
     * for each monster in the monster database,
     *     print it with printCsvRow()
     */
}

int foundColumnName(char columnName[], char followedBy)
/* helper function used to read an expected column name */
{
    char buffer[2048]; /* longer than the longest possible column name */
    /*
     * The "%[^,\n]s" format string tells scanf() to accept anything
     * that's not a ',' or a '\n' as part of a string. Those two
     * characters *separate* data in a CSV file.
     *
     * After reading and verifying the column name, we call getchar()
     * to verify that it's followed by the char `followedBy`,
     * returning 0 if any of these errors are detected.
     */
    if (scanf("%[^,\n]s",  buffer) != 1     /* didn't read one item */
        || strcmp(buffer, columnName) != 0  /* doesn't match `columnName` */
        || getchar() != followedBy) {       /* not followed by expected char */
        return 0;
    }
    return 1;
}


int scanCsvHeader(void)
/* scans a CSV-compatible "monster" header for compatibility */
{
    /*
     * This function will read a CSV header line and verify its
     * correctness. Return 1 if it is correct and 0 if it is not.
     *
     * You need to confirm that each header field is occurring in
     * order.  The function foundColumnName() can do the dirty work
     * here. All you need to do is call it properly.
     *
     * Here is how you do it for the first field in the header. 
     */
    if (!foundColumnName("Name", ','))
        return 0;
    /*
     * ASSIGNMENT
     *
     * Now do the same for the remaining attribute names in the
     * Monster struct, except that the last attribute should have the
     * `followedBy` argument be a newline ('\n'), not a comma.
     *
     * At the very end, all of the column names have been matched, so
     * return 1 to indicate success.
     */
}


int scanCsvRow(struct Monster *monster)
/* reads a Monster row from a CSV file */
{
    /*
     * This function calls scanf() to read every attribute on the row
     * in order using the appropriate format. The result of scanf()
     * will be 1 if the data is read successfully.
     *
     * For example, here's how you read the name. For string
     * attributes *only*, we again use the "%[^,\n]s" format string to
     * read a sequence of any characters except comma and newline.
     * Other attribute types can use the usual "%d", "%f", or
     * whatever.
     */
    if (scanf("%[^,\n]s",  monster->name) != 1)
        return 0;
    getchar(); /* skip the following comma or newline (we could check this) */
    /*
     * ASSIGNMENT
     *
     * Now do the same for the remaining attribute values in the
     * Monster struct. Be sure to that you always pass either a
     * reference (i.e. prefaced with "&") or the name of an array to
     * scanf().
     *
     * At the very end, all of the column names have been matched, so
     * return 1 to indicate success.
     */
}


int loadDatabase(void)
/* reads a CSV monster database on standard input, returns the number of members */
{
    /*
     * ASSIGNMENT
     *
     * Here's the pseudocode:
     *
     * call `scanCsvHeader()` to read the CSV header
     * if the CSV header is not correct,
     *     return 0
     * set `count` to 0
     * repeat the following indefinitely,
     *     call `scanCsvRow()` to read a new monster into the database
     *      at `monsters[count]`
     *     if it's not successful (i.e. at end-of-file),
     *         break out of the loop
     *     increment `count`
     * make `monsters[count].name` a zero-length string
     * return `count`
     */
}


int compareMonstersByYear(const void *monster0_vp, const void *monster1_vp)
{
    /*
     * Don't worry about this function for now. All you need to know
     * is that it's used by `qsort()` for sorting.
     */
    const struct Monster *monster0_p = monster0_vp;
    const struct Monster *monster1_p = monster1_vp;

    /* sort monsters by year of film release */
    return monster0_p->year - monster1_p->year;
}


int main(void)
{
    int monsterCount;

    monsterCount = loadDatabase();
    if (monsterCount > 0) {
        qsort(monsters, monsterCount, sizeof(struct Monster),
              compareMonstersByYear);
        saveDatabase(monsters);
    }
    return 0;
}

  1. Implement the pseudocode described in the program comments. You will need code from Lab 7 (Structures).

  2. Compile your monster_sortdb as usual.

  3. Download monsters.csv from the same course web link.

  4. Run the following:

    $ monster_sortdb <monsters.csv >sorted.csv
    
  5. Visually inspect sorted.csv to confirm that it's still got the header and that nothing else has changed, except that the monsters are now sorted by the year in which their films were released.

    While you can do this by viewing sorted.csv in a text editor or running "$ cat sorted.csv" in the console window, an even better way is to open it with a spreadsheet program like Microsoft Excel, LibreOffice Calc, or gnumeric, all of which support CSV.

Spreadsheet programs that can read CSV can also write it. This means you can use a spreadsheet to collect data, save it in a CSV file, and then use your own program to read, compute, and write back new results. This is a very convenient way to manage scientific, engineering, and other data.

Part 2: Converting a CSV File to HTML

(Note that you're getting fewer instructions here. You should be able to figure out what's needed here by now.)

Combine code from Part 1 of this homework with that of Part 2 of Lab 7 to create a program that can read a Monster database from a CSV file on standard input and write its equivalent in HTML to standard output.

Call the program monster_csv_to_html. The end result should be:

$ monster_csv_to_html <monsters.csv >monsters.html

and then monsters.html should be viewable in a browser, just as it was in Lab 7.

Email your monster_sortdb.c and your monster_csv_to_html.c to the instructor. If you have your own version of monsters.csv, you may also send that (just for fun, no extra credit).