efl/src/lib/efreet/efreet_xml.c

610 lines
14 KiB
C

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <Ecore_File.h>
/* define macros and variable for using the eina logging system */
#define EFREET_MODULE_LOG_DOM _efreet_xml_log_dom
static int _efreet_xml_log_dom = -1;
#include "Efreet.h"
#include "efreet_private.h"
#include "efreet_xml.h"
#if 0
static void efreet_xml_dump(Efreet_Xml *xml, int level);
#endif
static Efreet_Xml *efreet_xml_parse(char **data, int *size);
static int efreet_xml_tag_parse(char **data, int *size, const char **tag);
static void efreet_xml_attributes_parse(char **data, int *size,
Efreet_Xml_Attribute ***attributes);
static void efreet_xml_text_parse(char **data, int *size, const char **text);
static int efreet_xml_tag_empty(char **data, int *size);
static int efreet_xml_tag_close(char **data, int *size, const char *tag);
static void efreet_xml_cb_attribute_free(void *data);
static void efreet_xml_comment_skip(char **data, int *size);
static int error = 0;
static int _efreet_xml_init_count = 0;
/**
* @internal
* @return Returns > 0 on success or 0 on failure
* @brief Initialize the XML parser subsystem
*/
int
efreet_xml_init(void)
{
_efreet_xml_init_count++;
if (_efreet_xml_init_count > 1) return _efreet_xml_init_count;
_efreet_xml_log_dom = eina_log_domain_register
("efreet_xml", EFREET_DEFAULT_LOG_COLOR);
if (_efreet_xml_log_dom < 0)
{
_efreet_xml_init_count--;
EINA_LOG_ERR("Efreet: Could not create a log domain for efreet_xml.");
return _efreet_xml_init_count;
}
return _efreet_xml_init_count;
}
/**
* @internal
* @returns the number of initializations left for this system
* @brief Attempts to shut down the subsystem if nothing else is using it
*/
void
efreet_xml_shutdown(void)
{
_efreet_xml_init_count--;
if (_efreet_xml_init_count > 0) return;
eina_log_domain_unregister(_efreet_xml_log_dom);
_efreet_xml_log_dom = -1;
}
/**
* @internal
* @param file The file to parse
* @return Returns an Efreet_Xml structure for the given file @a file or
* NULL on failure
* @brief Parses the given file into an Efreet_Xml structure.
*/
Efreet_Xml *
efreet_xml_new(const char *file)
{
Efreet_Xml *xml = NULL;
int size, fd = -1;
char *data = MAP_FAILED;
struct stat st;
if (!file) return NULL;
fd = open(file, O_RDONLY);
if (fd == -1) goto efreet_error;
if (fstat(fd, &st) < 0) goto efreet_error;
/* let's make mmap safe and just get 0 pages for IO erro */
eina_mmap_safety_enabled_set(EINA_TRUE);
data = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) goto efreet_error;
error = 0;
size = st.st_size;
xml = efreet_xml_parse(&data, &size);
if (!xml || error) goto efreet_error;
munmap(data, st.st_size);
close(fd);
return xml;
efreet_error:
ERR("could not parse xml file");
if (data != MAP_FAILED) munmap(data, size);
if (fd != -1) close(fd);
if (xml) efreet_xml_del(xml);
return NULL;
}
/**
* @internal
* @param xml The Efree_Xml to free
* @return Returns no value
* @brief Frees up the given Efreet_Xml structure
*/
void
efreet_xml_del(Efreet_Xml *xml)
{
IF_FREE_LIST(xml->children, efreet_xml_cb_attribute_free);
if (xml->tag) eina_stringshare_del(xml->tag);
if (xml->attributes)
{
Efreet_Xml_Attribute **curr;
curr = xml->attributes;
while (*curr)
{
eina_stringshare_del((*curr)->key);
eina_stringshare_del((*curr)->value);
FREE(*curr);
curr++;
}
FREE(xml->attributes);
}
IF_RELEASE(xml->text);
FREE(xml);
}
/**
* @param xml The xml struct to work with
* @param key The attribute key to look for
* @return Returns the value for the given key, or NULL if none found
* @brief Retrieves the value for the given attribute key
*/
const char *
efreet_xml_attribute_get(Efreet_Xml *xml, const char *key)
{
Efreet_Xml_Attribute **curr;
if (!xml || !key || !xml->attributes) return NULL;
for (curr = xml->attributes; *curr; curr++)
{
if (!strcmp((*curr)->key, key))
return (*curr)->value;
}
return NULL;
}
static void
efreet_xml_cb_attribute_free(void *data)
{
efreet_xml_del(data);
}
#if 0
static void
efreet_xml_dump(Efreet_Xml *xml, int level)
{
int i;
for (i = 0; i < level; i++)
printf("\t");
printf("<%s", xml->tag);
if (xml->attributes)
{
Efreet_Xml_Attribute **curr;
for (curr = xml->attributes; *curr; curr++)
printf(" %s=\"%s\"", (*curr)->key, (*curr)->value);
}
if (xml->children)
{
Efreet_Xml *child;
Eina_List *l;
printf(">");
EINA_LIST_FOREACH(xml->children, l, child)
efreet_xml_dump(child, level + 1);
for (i = 0; i < level; i++)
printf("\t");
printf("</%s>", xml->tag);
}
else if (xml->text)
printf(">%s</%s>\n", xml->text, xml->tag);
else
printf("/>\n");
}
#endif
static Efreet_Xml *
efreet_xml_parse(char **data, int *size)
{
Efreet_Xml *xml, *sub_xml;
const char *tag = NULL;
/* parse this tag */
if (!efreet_xml_tag_parse(data, size, &(tag))) return NULL;
xml = NEW(Efreet_Xml, 1);
if (!xml)
{
eina_stringshare_del(tag);
return NULL;
}
xml->children = NULL;
xml->tag = tag;
efreet_xml_attributes_parse(data, size, &(xml->attributes));
/* Check wether element is empty */
if (efreet_xml_tag_empty(data, size)) return xml;
efreet_xml_text_parse(data, size, &(xml->text));
/* Check wether element is closed */
if (efreet_xml_tag_close(data, size, xml->tag)) return xml;
while ((sub_xml = efreet_xml_parse(data, size)))
xml->children = eina_list_append(xml->children, sub_xml);
efreet_xml_tag_close(data, size, xml->tag);
return xml;
}
static int
efreet_xml_tag_parse(char **data, int *size, const char **tag)
{
const char *start = NULL, *end = NULL;
char buf[256];
int buf_size;
/* Search for tag */
while (*size > 1)
{
/* Check for tag start */
if (**data == '<')
{
/* Check for end tag */
if (*(*data + 1) == '/') return 0;
/* skip comments */
if (*size > 3 && *(*data + 1) == '!' && *(*data + 2) == '-' && *(*data + 3) == '-')
{
(*data) += 3;
(*size) -= 3;
efreet_xml_comment_skip(data, size);
continue;
}
/* Check for xml directives (and ignore them) */
else if ((*(*data + 1) != '!') && (*(*data + 1) != '?'))
{
(*size)--;
(*data)++;
start = *data;
break;
}
}
(*size)--;
(*data)++;
}
if (!start)
{
ERR("missing start tag");
error = 1;
return 0;
}
while (*size > 0)
{
if (!isalpha(**data))
{
end = *data;
break;
}
(*size)--;
(*data)++;
}
if (!end)
{
ERR("no end of tag");
error = 1;
return 0;
}
buf_size = end - start + 1;
if (buf_size <= 1)
{
ERR("no tag name");
error = 1;
return 0;
}
if (buf_size > 256) buf_size = 256;
memcpy(buf, start, buf_size - 1);
buf[buf_size - 1] = '\0';
*tag = eina_stringshare_add(buf);
return 1;
}
static void
efreet_xml_attributes_parse(char **data, int *size,
Efreet_Xml_Attribute ***attributes)
{
Efreet_Xml_Attribute attr[10];
int i, count = 0;
while (*size > 0)
{
if (**data == '>')
{
(*size)++;
(*data)--;
break;
}
else if ((count < 10) && (isalpha(**data)))
{
/* beginning of key */
const char *start = NULL, *end = NULL;
char buf[256];
int buf_size;
attr[count].key = NULL;
attr[count].value = NULL;
start = *data;
while ((*size > 0) && ((isalpha(**data)) || (**data == '_')))
{
(*size)--;
(*data)++;
}
end = *data;
buf_size = end - start + 1;
if (buf_size <= 1)
{
ERR("zero length key");
goto efreet_error;
}
if (buf_size > 256) buf_size = 256;
memcpy(buf, start, buf_size - 1);
buf[buf_size - 1] = '\0';
attr[count].key = eina_stringshare_add(buf);
/* search for '=', key/value seperator */
start = NULL;
while (*size > 0)
{
if (**data == '=')
{
start = *data;
break;
}
(*size)--;
(*data)++;
}
if (!start)
{
ERR("missing value for attribute!");
goto efreet_error;
}
/* search for '"', beginning of value */
start = NULL;
while (*size > 0)
{
if (**data == '"')
{
start = *data;
break;
}
(*size)--;
(*data)++;
}
if (!start)
{
ERR("erroneous value for attribute!");
goto efreet_error;
}
/* skip '"' */
start++;
(*size)--;
(*data)++;
/* search for '"', end of value */
end = NULL;
while (*size > 0)
{
if (**data == '"')
{
end = *data;
break;
}
(*size)--;
(*data)++;
}
if (!end)
{
ERR("erroneous value for attribute!");
goto efreet_error;
}
buf_size = end - start + 1;
if (buf_size <= 1)
{
ERR("zero length value");
goto efreet_error;
}
if (buf_size > 256) buf_size = 256;
memcpy(buf, start, buf_size - 1);
buf[buf_size - 1] = '\0';
attr[count].value = eina_stringshare_add(buf);
count++;
}
(*size)--;
(*data)++;
}
*attributes = NEW(Efreet_Xml_Attribute *, count + 1);
if (!*attributes) goto efreet_error;
for (i = 0; i < count; i++)
{
(*attributes)[i] = malloc(sizeof(Efreet_Xml_Attribute));
(*attributes)[i]->key = attr[i].key;
(*attributes)[i]->value = attr[i].value;
}
return;
efreet_error:
while (count >= 0)
{
if (attr[count].key) eina_stringshare_del(attr[count].key);
if (attr[count].value) eina_stringshare_del(attr[count].value);
count--;
}
error = 1;
return;
}
static void
efreet_xml_text_parse(char **data, int *size, const char **text)
{
const char *start = NULL, *end = NULL;
int buf_size;
/* skip leading whitespace */
while (*size > 0)
{
if (!isspace(**data))
{
start = *data;
break;
}
(*size)--;
(*data)++;
}
if (!start) return;
/* find next tag */
while (*size > 0)
{
if (**data == '<')
{
end = *data;
break;
}
(*size)--;
(*data)++;
}
if (!end) return;
/* skip trailing whitespace */
while (isspace(*(end - 1))) end--;
/* copy text */
buf_size = end - start + 1;
if (buf_size <= 1) return;
*text = eina_stringshare_add_length(start, buf_size - 1);
}
static int
efreet_xml_tag_empty(char **data, int *size)
{
while (*size > 1)
{
if (**data == '/')
{
(*size)--;
(*data)++;
if (**data == '>')
{
(*size)--;
(*data)++;
return 1;
}
}
else if (**data == '>')
{
(*size)--;
(*data)++;
return 0;
}
(*size)--;
(*data)++;
}
ERR("missing end of tag");
error = 1;
return 1;
}
static int
efreet_xml_tag_close(char **data, int *size, const char *tag)
{
while (*size > 1)
{
if (**data == '<')
{
if (*(*data + 1) == '/')
{
(*size) -= 2;
(*data) += 2;
if ((int)strlen(tag) > *size)
{
ERR("wrong end tag");
error = 1;
return 1;
}
else
{
char *tmp;
tmp = *data;
while ((*tag) && (*tmp == *tag))
{
tmp++;
tag++;
}
if (*tag)
{
ERR("wrong end tag");
error = 1;
return 1;
}
}
return 1;
}
else return 0;
}
(*size)--;
(*data)++;
}
return 0;
}
static void
efreet_xml_comment_skip(char **data, int *size)
{
while (*size > 2)
{
if (**data == '-' && *(*data + 1) == '-' && *(*data + 2) == '>')
{
(*data) += 3;
(*size) -= 3;
return;
}
(*data)++;
(*size)--;
}
}