Posts in category eina

Eina_Simple_XML Tutorial

Eina_Simple_XML is a library that should make it easy to handle XML files while consuming very little (or no) memory. Before we start I've got to mention the caveat: to preserve simplicity Eina_Simple_XML does not implement a few XML features such as namespaces, encoding and entities, it's however capable of parsing simple XML files.

Eina_Simple_XML provides a  SAX like API, you give it a string and a callback function to be called every time it finds a relevant token. This is all a lot easier to understand by looking at an example, so let's do that. We are first going to parse and then add to this XML file:

<phonebook>
        <contact>
                <name>John Doe</name>
                <phone>+55(19)5555-7777</phone>
                <email>john.doe@email.com</email>
                <dob>1968-09-19</dob>
        </contact>
        <contact>
                <name>Jane Doe</name>
                <phone>+55(19)6666-7777</phone>
                <email>jane.doe@email.com</email>
                <dob>1968-09-21</dob>
        </contact>
        <contact>
                <name>Bill</name>
                <phone>+55(19)7777-7777</phone>
                <email>bill@cool_emails.com</email>
                <dob>1968-09-23</dob>
        </contact>
</phonebook>

In the parsing stage we're going to not just look at the data but actually populate a list of contacts:

typedef struct _contact
{
    char *name;
    char *phone;
    char *email;
    char *dob;
} contact;

Now that we've defined the data structure to be used we need to create the function that we'll use as a callback to the parser. This function treats 4 situations, the first is the contact open tag, the second is the open tag of name, phone, email or dob, the third is for data elements and finally the contact close tag. In the first case we create a new contact, in the second we set a state variable so that we know where to put the data we receive next, and the final situation is when the contact is fully created and we add it to the list.

typedef enum _State
{
    NAME,
    PHONE,
    EMAIL,
    DOB
} State;

static Eina_List *contacts;

static Eina_Bool _parse(void *data, Eina_Simple_XML_Type type, const char *content, unsigned offset, unsigned length)
{
    static contact *cur = NULL;
    static State s;

    if(type == EINA_SIMPLE_XML_OPEN && !strncmp("contact", content, length))
        cur = malloc(sizeof(contact));
    else if(cur && type == EINA_SIMPLE_XML_OPEN) {
        if(!strncmp("name", content, length))
            s = NAME;
        else if(!strncmp("phone", content, length))
            s = PHONE;
        else if(!strncmp("email", content, length))
            s = EMAIL;
        else if(!strncmp("dob", content, length))
            s = DOB;
    } else if(cur && type == EINA_SIMPLE_XML_DATA) {
        char *ptr = strndup(content, length);
        switch(s) {
            case NAME:
                cur->name = ptr;
                break;
            case PHONE:
                cur->phone = ptr;
                break;
            case EMAIL:
                cur->email = ptr;
                break;
            case DOB:
                cur->dob = ptr;
                break;
        }
    } else if(cur && type == EINA_SIMPLE_XML_CLOSE && !strncmp("contact", content, length)) {
        contacts = eina_list_append(contacts, cur);
        cur = NULL;
    }
    return EINA_TRUE;
}

Creating a new contact is even simpler, as you can see the function creates a contact tag, and a name, phone, email and dob tag. When creating each tag we specify the parent and after the tag is created we add a data node as it's child:

void create_contact(Eina_Simple_XML_Node_Tag *phonebook, const char *n, const char *p, const char *e, const char *d) {
    Eina_Simple_XML_Node_Tag *c, *n_tag, *p_tag, *e_tag, *d_tag;
    c = eina_simple_xml_node_tag_new(phonebook, "contact");
    n_tag = eina_simple_xml_node_tag_new(c, "name");
    eina_simple_xml_node_data_new(n_tag, n, strlen(n));
    p_tag = eina_simple_xml_node_tag_new(c, "phone");
    eina_simple_xml_node_data_new(p_tag, p, strlen(p));
    e_tag = eina_simple_xml_node_tag_new(c, "email");
    eina_simple_xml_node_data_new(e_tag, e, strlen(e));
    d_tag = eina_simple_xml_node_tag_new(c, "dob");
    eina_simple_xml_node_data_new(d_tag, d, strlen(d));
}

So we've seen a function to use as a callback in parsing the phonebook and a function to create contacts in the phonebook, now we look at how we tie all of this together. Our main function does a quite a bit of boiler plate stuff that I'll leave as an exercise to the reader to figure out.

int main(int argc, char **argv)
{
    //Just declaring and initializing some variables
    Eina_List *l;
    char *buf, ch;
    int i;
    FILE *f = fopen("phonebook.xml", "r");
    Eina_Simple_XML_Node_Root *root;
    Eina_Simple_XML_Node *phonebook;
    contact *cur;

    eina_init(); //Always initialize eina before using it

    //Here we read the contents of the file into a variable and then close the file
    fseek(f, 0, SEEK_END);
    buf = malloc(sizeof(char) * ftell(f)); //We don't care about the EOF char
    fseek(f, 0, SEEK_SET);

    for(i = 0; (ch = fgetc(f)) != EOF; i++)
        buf[i] = ch;
    fclose(f); //Free resources as soon as we're done with done

    eina_simple_xml_parse(buf, i, EINA_TRUE, _parse, NULL);

    EINA_LIST_FOREACH(contacts, l, cur)
        printf("%s: %s\n%s - %s\n\n", cur->name, cur->phone, cur->email, cur->dob);
    EINA_LIST_FREE(contacts, cur) {
        free(cur->name);
        free(cur->phone);
        free(cur->email);
        free(cur->dob);
        free(cur);
    }

    root = eina_simple_xml_node_load(buf, i, EINA_TRUE);
    free(buf); //We won't need this anymore so free

    phonebook = EINA_INLIST_CONTAINER_GET(root->children, Eina_Simple_XML_Node);
    create_contact((Eina_Simple_XML_Node_Tag*)phonebook, "greg", "0000-1111", "greg@good-guy.com", "1988-02-30");
    buf = eina_simple_xml_node_dump((Eina_Simple_XML_Node*)root, "\t");

    //Now that we have the new XML in a variable let's put it back in the file
    f = fopen("phonebook.xml", "w");
    fputs(buf, f);
    fclose(f);
    free(buf);
    eina_simple_xml_node_root_free(root);

    eina_shutdown();

    return 0;
}

The first bit here that we should look at is the eina_simple_xml_parse() call, this is what is doing all the work and calling our _parse() function, the first parameter we give it is the content of the phonebook.xml file, the second the size(in chars/bytes), the third is an EINA_BOOL telling it to trim whitespace, finally we give it the callback function and a NULL since we don't need to receive anything on it. Just to make sure the parsing worked we then print the list of contacts and free it.

That's all for the parsing, let's now talk about the creation of another contact. The first thing to do is to have Eina_Simple_XML create a tree of the document for us and get the root element, which is a single call to eina_simple_xml_node_load(). Once we have the root node we get it's first(and only) child, which, since we know the structure of the document, we know is the "phonebook" node, from then on it's a simple matter of calling the previously discussed(and shown) function and eina_simple_xml_node_dump() to get the resulting XML. The rest of the code is just file and memory handling, which should be very straitghforward and easy to understand.

Now that I've thrown all this code at you I need to give another warning, DON'T use this as is. If you're wandering why I showed you all of this if you shouldn't use, it's a simple matter, to avoid polluting the code with a lot of stuff that isn't directly relevant I removed all error checking. If you want to parse your own XML files feel free to use this code as a base and modify as needed(e.g.: add error checking =) ).

Introducing Eina Logging module

Hi all, developers and users, this topic should be of interest of you: logging.

Update: I did a replica of this page in wiki as EinaLog, please add improvements and suggestions there so we use it as documentation.

Users

For users, this will help you to figure out source of problems. Developers may ask you the output and give you the exact command line to run, but one example is:

EINA_LOG_LEVEL=4 expedite -e xlib

This will run expedite benchmark tool with all logging domains at level 4 (debug, the greater the number, the more verbose it will be). It should show lots of output lines like:

DBG:eina_module eina_amalgamation.c:8811 eina_module_list_load() array 0x65ca10, count 0

The line is easily grep-able, the first 3 letters are the log level name or number if it's less than zero or greater than 4. Then follows the domain name (eina_module in the example) followed by the source file (eina_amalgamation.c), line (8811), function name (eina_module_list_load) and then the message.

It should be colored as well, making it easier to spot different levels and domains.

To disable colors, use the environment variable EINA_LOG_COLOR_DISABLE=1

If one dislikes having both file/line and function (often they are redundant), then disable one of them with EINA_LOG_FILE_DISABLE=1 or EINA_LOG_FUNCTION_DISABLE=1. Do not use both, in that case just function will be disabled as file/line is more specific (and thus useful).

If program is multi-threaded and thread-safe logging is initialized (eina_log_threads_enable()), then logging from other threads (any other thread than the one that called eina_init()) you'll see another component: [T:XXXX] where XXXX is the thread number. This is to make sure the code is running in the proper place.

What is interesting about eina log is domain support. This enables us to see debug log for one module while seeing information for another and just warnings for the rest. Let's say we want to see debug for eina_module, info for eina_stringshare but just warning for everything else, we'd use the following command:

EINA_LOG_LEVEL=2 EINA_LOG_LEVELS=eina_module:4,eina_stringshare:3 expedite -e xlib

The first variable sets the generic log, as explained above. The new variable EINA_LOG_LEVELS specifies a comma separated list of domain:level. Domain names are specified in source code and is not dependent on file name, so developers should document available domains.

Developers

For developers it's easy: either use the global domain EINA_LOG_DOMAIN_GLOBAL or register your own domain.

Using the global domain can be handy to quick debug, but it's not recommended for the long run. To use it, one can use pre-defined macros: EINA_LOG_CRIT(), EINA_LOG_ERR(), EINA_LOG_WARN(), EINA_LOG_INFO() and EINA_LOG_DBG(). These macros are like printf() and should be easy to convert an application using printf() or fprintf() to it.

To register your own domain it's as simple as calling the function eina_log_domain_register(), it returns the log domain identifier that should be used with macros like EINA_LOG(), EINA_LOG_DOM_CRIT(), EINA_LOG_DOM_ERR() and so on. To make life easier, it's advised that applications declare their own macros. Example:

Using in a single C file


#include <Eina.h>

static int _log_dom = -1

#define ERR(...) EINA_LOG_DOM_ERR(_log_dom, __VA_ARGS__)
#define DBG(...) EINA_LOG_DOM_DBG(_log_dom, __VA_ARGS__)

Eina_Bool my_code_init(void) {
   if (!eina_init()) { /* you should init eina before registering log */
      return EINA_FALSE;
   }

   if (_log_dom < 0) {
      _log_dom = eina_log_domain_register("my_code", NULL);
      if (_log_dom < 0) {
         EINA_LOG_CRIT("could not register log domain 'my_code'");
         eina_shutdown();
         return EINA_FALSE;
      }
   }

   DBG("initialized!");
   return EINA_TRUE;
}

void my_code_shutdown(void) {
    if (_log_dom >= 0) {
       DBG("shutdown!");
       eina_log_domain_unregister(_log_dom);
       _log_dom = -1;
       eina_shutdown();
    }
}

void my_code_func(int val) {
    DBG("val=%d", val);
    if (val < 1) ERR("val less than 1, val=%d", val);
}

Using from multiple C files

Your private (my_code_private.h) header should contain:


#include <Eina.h>
extern int _my_code_log_dom = -1

#define ERR(...) EINA_LOG_DOM_ERR(_my_code_log_dom, __VA_ARGS__)
#define DBG(...) EINA_LOG_DOM_DBG(_my_code_log_dom, __VA_ARGS__)

Your main source file:


#include "my_code_private.h"

Eina_Bool my_code_init(void) {
   if (!eina_init()) { /* you should init eina before registering log */
      return EINA_FALSE;
   }

   if (_log_dom < 0) {
      _my_code_log_dom = eina_log_domain_register("my_code", NULL);
      if (_my_code_log_dom < 0) {
         EINA_LOG_CRIT("could not register log domain 'my_code'");
         eina_shutdown();
         return EINA_FALSE;
      }
   }

   DBG("initialized!");
   return EINA_TRUE;
}

void my_code_shutdown(void) {
    if (_my_code_log_dom >= 0) {
       DBG("shutdown!");
       eina_log_domain_unregister(_log_dom);
       _my_code_log_dom = -1;
       eina_shutdown();
    }
}

Your secondary files:


#include "my_code_private.h"

void my_code_func(int val) {
    DBG("val=%d", val);
    if (val < 1) ERR("val less than 1, val=%d", val);
}

Extra developer features

One can define EINA_LOG_ABORT=1 to make eina abort on levels less or equal to critical. This level is used by safety checks and other paths and can make debug easier, just run it from inside gdb and it will stop on failure.