mel documentation

Automated XML handling in C!

For the impatient:


shell$ $MELDIR/mel -I$MELDIR -I/usr/include/libxml2 -r week week.dtd

shell$ cat > reader.c
#include "week.h"
int main() {
	FILE * outputfile;
	struct week *week = read_week(week.xml);
	/* do stuff with the structures returned */
	outputfile = fopen("weeknew.xml", "w");
	print_week(week, outputfile);
}
^D
That should get you started with mel.

This documentation assumes you know how to read a DTD and programme in C. We'll start with a simple DTD.


	<!ELEMENT week (day*)>
	<!ELEMENT day (date, quote*)>
	<!ELEMENT date (year, month, dom)>
	<!ELEMENT month (#PCDATA)>
	<!ELEMENT year (#PCDATA)>
	<!ELEMENT dom (#PCDATA)>
	<!ELEMENT quote (name, close, low, high)>
	<!ELEMENT name (#PCDATA)>
	<!ELEMENT close (#PCDATA)>
	<!ELEMENT low (#PCDATA)>
	<!ELEMENT high (#PCDATA)>
This is for a small stock program. It stores a "week"'s worth of data about stockmarket data. Each week is made up of days, each day includes the date and a list of quotes which in turn are have a name, a closing price, a high and a low for the day.

We store that in the file week.dtd and in the Makefile, we place the line:


week.h week.c:
	$(MELDIR)/mel -I$(MELDIR) -I/usr/include/libxml2 -r week week.dtd
The -r means that we want week to be the root (and by implication the name of the produced .h and .c files). $(MELDIR) is the directory where mel is installed and includes the files mel, mel.c.template, mel.h.template, melstack.h, mellib.h and mellib.a

The week.h file will look like this:


#include "melstack.h"
#include 
#include 
#include 
#include 

/** THIS FILE IS AUTO-GENERATED
 * Do NOT edit!!
 * edit the .dtd file instead to alter these structures
 */



struct close {
	xmlChar *_chars;
};

struct date {
	struct year *year;
	struct month *month;
	struct dom *dom;
};

struct day {
	struct date *date;
	Stack(struct quote, quote);
};

struct dom {
	xmlChar *_chars;
};

struct high {
	xmlChar *_chars;
};

struct low {
	xmlChar *_chars;
};

struct month {
	xmlChar *_chars;
};

struct name {
	xmlChar *_chars;
};

struct quote {
	struct name *name;
	struct close *close;
	struct low *low;
	struct high *high;
};

struct week {
	Stack(struct day, day);
	struct mel_idTree *mel_IDTree;
};

struct year {
	xmlChar *_chars;
};
/* order */
enum week_element_order {
	MEL_close,
	MEL_date,
	MEL_day,
	MEL_dom,
	MEL_high,
	MEL_low,
	MEL_month,
	MEL_name,
	MEL_quote,
	MEL_week,
	MEL_year
};


struct week *read_week(const char * filename);

void print_week(struct week* data, FILE *out);

void print_week_element(void *data, enum week_element_order type, FILE *out);

void debug_week(FILE *out);

void free_week_element(void *data, enum week_element_order type);

void free_week(struct week*data);
struct mel_element *week_idref2Element(struct week *root, const xmlChar * idref);
struct mel_element *week_addIdrefNElement(struct week *root, const xmlChar * idref, struct mel_element *element);
int validate_week(struct week *root);

Let's go over that in pieces


#include "melstack.h"
#include 
#include 
#include 
#include 
These are various files that allow it to compile when compiled with gcc with extreme warnings turned on. Note "melstack.h". This is a template created with the C preprocessor to allow collections of items of all the same type.

/** THIS FILE IS AUTO-GENERATED
 * Do NOT edit!!
 * edit the .dtd file instead to alter these structures
 */
This comment is self-evident. Do what it says and don't edit this file.

struct close {
	xmlChar *_chars;
};
Many of the structures in this file look like this. PCDATA types map nicely to strings of xmlChar (from the libxml2 library). You can read it, replace it, do whatever you want with it.

struct date {
	struct year *year;
	struct month *month;
	struct dom *dom;
};
This is a more interesting structure. It just says date includes the year, the month and the day of the month. Same as the DTD said.

struct day {
	struct date *date;
	Stack(struct quote, quote);
};
This is more interesting. day includes a date and a collection of quotes. The Stack macro represents an expandable array. You can push struct quote *'s onto it until you run out of memory (though this is not recommended since it will spend a long time copying) and then pop all of the pointers back off. You can also run through the list of pushed items using a loop like

	for (j = 0; j < Stack_size(&day.quote); j += 1){
		struct quote * currquote = Stack_get(&day.quote, j);
		/* do whatever */
	}

which will read through the quotes in the order they were pushed onto the stack.

We'll skip the next few since they are identical to close


struct week {
	Stack(struct day, day);
	struct mel_idTree *mel_IDTree;
};
sturct mel_idTree is a tree of ID's to resolve IDREFs. This is one case where your compiler won't catch you assigning the wrong type to a pointer since the IDTree stores everything as void *

struct year {
	xmlChar *_chars;
};
/* order */
enum week_element_order {
	MEL_close,
	MEL_date,
	MEL_day,
	MEL_dom,
	MEL_high,
	MEL_low,
	MEL_month,
	MEL_name,
	MEL_quote,
	MEL_week,
	MEL_year
};
This is a the list of types in the DTD. It's used internally by mellib and so that you can tell mellib to read something that isn't a week. More a few lines down.

struct week *read_week(const char * filename);
This is your basic way of reading a XML file. If we had given a different root name, we would have gotten a different name for this function. It should be safe to hook up multiple produced readers to mellib and still get intelligible results, though I admit, I haven't tried it.

void print_week(struct week* data, FILE *out);
This will print the XML back out.

void print_week_element(void *data, enum week_element_order type, FILE *out);
Here's where that last enumeration is useful. We can tell mellib to write out just a portion of the XML. Say those parts under quote by calling

	print_week_element(quote, MEL_quote, stdout);


void debug_week(FILE *out);
This is for debugging mellib. You're welcome to see what it does, but do expect lots of impossible to read information to pop up.

void free_week_element(void *data, enum week_element_order type);

void free_week(struct week*data);
These two are to free the XML data. You could easily write these yourself, but I'm too nice a guy.

struct mel_element *week_idref2Element(struct week *root, const xmlChar * idref);
struct mel_element *week_addIdrefNElement(struct week *root, const xmlChar * idref, struct mel_element *element);
These are for looking up and adding IDs to the XML. I've not actually tried them out other than debugging them. They would be used like this:

	quote = week_idref2Element(week, "BOBS");
assuming that quote had an ID attribute associated with it.

int validate_week(struct week *root);
This will read through a week and verify that all required fields that mellib understand are set.

So, how do you acutally use it? Try this on for size


#include "week.h"
static struct week * week;
const char * names[] = {
	"stock1",
	"stock2"
};

static int highs[sizeof(names)/sizeof(const char *)];
static void getHighs(struct quote *quote){
    int m;
    for (m = 0 ; m < 2; m += 1){
        if (!strcmp(names[m], quote->name->_chars)){
            int today = atoi(quote->high->_chars);
            if (highs[m] < today)
                highs[m] = today;
        }
    }
}


int main(int argc, char ** argv){
    int j,k,m;
    week = read_week("quotes.xml");
    for (j =0 ; j < Stack_size(&week->day); j += 1){
        struct day *day = Stack_get(&week->day,j);
        for (k = 0; k < Stack_size(&day->quote); k += 1){
            struct quote *quote = Stack_get(&day->quote, k);
            getHighs(quote);
        }
    }
    free_week(week);

    for (m = 0; m < sizeof(names)/sizeof(const char *); m += 1){
        printf("%s: %d\n", names[m], highs[m]);
    }
    
}

For more information see the Makefile and main.c in the testdir directory.