diff --git a/legacy/eina/ChangeLog b/legacy/eina/ChangeLog index 27d46b1cf4..97447a327a 100644 --- a/legacy/eina/ChangeLog +++ b/legacy/eina/ChangeLog @@ -37,3 +37,7 @@ * Use stringshare for eina_error messages * add eina_error_find to match an error message with its Eina_Error + +2011-04-06 Gustavo Sverzut Barbieri + + * Add Simple XML parser API. diff --git a/legacy/eina/src/include/Makefile.am b/legacy/eina/src/include/Makefile.am index f04b2978f8..4846588b6c 100644 --- a/legacy/eina/src/include/Makefile.am +++ b/legacy/eina/src/include/Makefile.am @@ -52,7 +52,8 @@ eina_inline_str.x \ eina_strbuf.h \ eina_ustrbuf.h \ eina_unicode.h \ -eina_quadtree.h +eina_quadtree.h \ +eina_simple_xml_parser.h installed_mainheaderdir = $(includedir)/eina-@VMAJ@ dist_installed_mainheader_DATA = Eina.h eina_config.h diff --git a/legacy/eina/src/include/eina_simple_xml_parser.h b/legacy/eina/src/include/eina_simple_xml_parser.h new file mode 100644 index 0000000000..6335daccfd --- /dev/null +++ b/legacy/eina/src/include/eina_simple_xml_parser.h @@ -0,0 +1,145 @@ +/* EINA - EFL data type library + * Copyright (C) 2011 Gustavo Sverzut Barbieri + * Cedric Bail + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; + * if not, see . + */ + +#ifndef EINA_SIMPLE_XML_H_ +#define EINA_SIMPLE_XML_H_ + +#include "eina_config.h" + +#include "eina_types.h" +#include "eina_magic.h" +#include "eina_inlist.h" + +/** + * @addtogroup Eina_Tools_Group Tools + * + * @{ + */ + +/** + * @defgroup Eina_Simple_XML_Group Simple_XML + * + * @{ + */ + +typedef struct _Eina_Simple_XML_Node Eina_Simple_XML_Node; +typedef struct _Eina_Simple_XML_Node_Tag Eina_Simple_XML_Node_Root; +typedef struct _Eina_Simple_XML_Node_Tag Eina_Simple_XML_Node_Tag; +typedef struct _Eina_Simple_XML_Node_Data Eina_Simple_XML_Node_Data; +typedef struct _Eina_Simple_XML_Node_Data Eina_Simple_XML_Node_CData; +typedef struct _Eina_Simple_XML_Node_Data Eina_Simple_XML_Node_Processing; +typedef struct _Eina_Simple_XML_Node_Data Eina_Simple_XML_Node_Doctype; +typedef struct _Eina_Simple_XML_Node_Data Eina_Simple_XML_Node_Comment; +typedef struct _Eina_Simple_XML_Attribute Eina_Simple_XML_Attribute; + +struct _Eina_Simple_XML_Attribute +{ + EINA_INLIST; + EINA_MAGIC; + + Eina_Simple_XML_Node_Tag *parent; + const char *key; + const char *value; +}; + +typedef enum _Eina_Simple_XML_Node_Type +{ + EINA_SIMPLE_XML_NODE_ROOT = 0, + EINA_SIMPLE_XML_NODE_TAG, + EINA_SIMPLE_XML_NODE_DATA, + EINA_SIMPLE_XML_NODE_CDATA, + EINA_SIMPLE_XML_NODE_PROCESSING, + EINA_SIMPLE_XML_NODE_DOCTYPE, + EINA_SIMPLE_XML_NODE_COMMENT +} Eina_Simple_XML_Node_Type; + +struct _Eina_Simple_XML_Node +{ + EINA_INLIST; + EINA_MAGIC; + + Eina_Simple_XML_Node_Tag *parent; + Eina_Simple_XML_Node_Type type; +}; + +struct _Eina_Simple_XML_Node_Tag +{ + Eina_Simple_XML_Node base; + Eina_Inlist *children; + Eina_Inlist *attributes; + const char *name; +}; + +struct _Eina_Simple_XML_Node_Data +{ + Eina_Simple_XML_Node base; + size_t length; + char data[]; +}; + +typedef enum _Eina_Simple_XML_Type +{ + EINA_SIMPLE_XML_OPEN = 0, /* */ + EINA_SIMPLE_XML_OPEN_EMPTY, /* */ + EINA_SIMPLE_XML_CLOSE, /* */ + EINA_SIMPLE_XML_DATA, /* tag text data */ + EINA_SIMPLE_XML_CDATA, /* */ + EINA_SIMPLE_XML_ERROR, /* error contents */ + EINA_SIMPLE_XML_PROCESSING, /* */ + EINA_SIMPLE_XML_DOCTYPE, /* */ + EINA_SIMPLE_XML_IGNORED /* whatever is ignored by parser, like whitespace */ +} Eina_Simple_XML_Type; + +typedef Eina_Bool (*Eina_Simple_XML_Cb)(void *data, Eina_Simple_XML_Type type, const char *content, unsigned offset, unsigned length); +typedef Eina_Bool (*Eina_Simple_XML_Attribute_Cb)(void *data, const char *key, const char *value); + +EAPI Eina_Bool eina_simple_xml_parse(const char *buf, unsigned buflen, + Eina_Bool strip, + Eina_Simple_XML_Cb func, const void *data); + +EAPI const char * eina_simple_xml_tag_attributes_find(const char *buf, unsigned buflen); +EAPI Eina_Bool eina_simple_xml_attributes_parse(const char *buf, unsigned buflen, + Eina_Simple_XML_Attribute_Cb func, const void *data); +EAPI Eina_Simple_XML_Attribute * eina_simple_xml_attribute_new(Eina_Simple_XML_Node_Tag *parent, const char *key, const char *value); +EAPI void eina_simple_xml_attribute_free(Eina_Simple_XML_Attribute *attr); + +EAPI Eina_Simple_XML_Node_Tag * eina_simple_xml_node_tag_new(Eina_Simple_XML_Node_Tag *parent, const char *name); +EAPI void eina_simple_xml_node_tag_free(Eina_Simple_XML_Node_Tag *tag); + +EAPI Eina_Simple_XML_Node_Data * eina_simple_xml_node_data_new(Eina_Simple_XML_Node_Tag *parent, const char *contents, size_t length); +EAPI void eina_simple_xml_node_data_free(Eina_Simple_XML_Node_Data *node); + +EAPI Eina_Simple_XML_Node_CData * eina_simple_xml_node_cdata_new(Eina_Simple_XML_Node_Tag *parent, const char *contents, size_t length); +EAPI void eina_simple_xml_node_cdata_free(Eina_Simple_XML_Node_Data *node); + +EAPI Eina_Simple_XML_Node_Processing * eina_simple_xml_node_processing_new(Eina_Simple_XML_Node_Tag *parent, const char *contents, size_t length); +EAPI void eina_simple_xml_node_processing_free(Eina_Simple_XML_Node_Data *node); + +EAPI Eina_Simple_XML_Node_Doctype * eina_simple_xml_node_doctype_new(Eina_Simple_XML_Node_Tag *parent, const char *contents, size_t length); +EAPI void eina_simple_xml_node_doctype_free(Eina_Simple_XML_Node_Data *node); + +EAPI Eina_Simple_XML_Node_Comment * eina_simple_xml_node_comment_new(Eina_Simple_XML_Node_Tag *parent, const char *contents, size_t length); +EAPI void eina_simple_xml_node_comment_free(Eina_Simple_XML_Node_Data *node); + +EAPI Eina_Simple_XML_Node_Root * eina_simple_xml_node_load(const char *buf, unsigned buflen, Eina_Bool strip); +EAPI void eina_simple_xml_node_root_free(Eina_Simple_XML_Node_Root *root); +EAPI char * eina_simple_xml_node_dump(Eina_Simple_XML_Node *node, const char *indent); + +#endif /* EINA_SIMPLE_XML_H_ */ diff --git a/legacy/eina/src/lib/Makefile.am b/legacy/eina/src/lib/Makefile.am index fac41ad198..afbe96484f 100644 --- a/legacy/eina/src/lib/Makefile.am +++ b/legacy/eina/src/lib/Makefile.am @@ -45,7 +45,8 @@ eina_tiler.c \ eina_unicode.c \ eina_ustrbuf.c \ eina_ustringshare.c \ -eina_value.c +eina_value.c \ +eina_simple_xml_parser.c if EINA_HAVE_WIN32 base_sources += eina_file_win32.c diff --git a/legacy/eina/src/lib/eina_main.c b/legacy/eina/src/lib/eina_main.c index 0ff5e1c1d3..48ca32c861 100644 --- a/legacy/eina/src/lib/eina_main.c +++ b/legacy/eina/src/lib/eina_main.c @@ -123,6 +123,7 @@ static HANDLE _mutex = NULL; S(strbuf); S(ustrbuf); S(quadtree); + S(simple_xml); #undef S struct eina_desc_setup @@ -154,7 +155,8 @@ static const struct eina_desc_setup _eina_desc_setup[] = { S(rectangle), S(strbuf), S(ustrbuf), - S(quadtree) + S(quadtree), + S(simple_xml) #undef S }; static const size_t _eina_desc_setup_len = sizeof(_eina_desc_setup) / diff --git a/legacy/eina/src/lib/eina_private.h b/legacy/eina/src/lib/eina_private.h index ccb6c071e3..2162b47cb3 100644 --- a/legacy/eina/src/lib/eina_private.h +++ b/legacy/eina/src/lib/eina_private.h @@ -90,6 +90,10 @@ #define EINA_MAGIC_QUADTREE_ROOT 0x98761252 #define EINA_MAGIC_QUADTREE_ITEM 0x98761253 +#define EINA_MAGIC_SIMPLE_XML_TAG 0x98761260 +#define EINA_MAGIC_SIMPLE_XML_DATA 0x98761261 +#define EINA_MAGIC_SIMPLE_XML_ATTRIBUTE 0x98761262 + /* undef the following, we want out version */ #undef FREE #define FREE(ptr) \ diff --git a/legacy/eina/src/lib/eina_simple_xml_parser.c b/legacy/eina/src/lib/eina_simple_xml_parser.c new file mode 100644 index 0000000000..097c8be210 --- /dev/null +++ b/legacy/eina/src/lib/eina_simple_xml_parser.c @@ -0,0 +1,1281 @@ +/* EINA - EFL data type library + * Copyright (C) 2011 Gustavo Sverzut Barbieri + * Cedric Bail + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; + * if not, see . + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_ALLOCA_H +# include +#elif defined __GNUC__ +# define alloca __builtin_alloca +#elif defined _AIX +# define alloca __alloca +#elif defined _MSC_VER +# include +# define alloca _alloca +#else +# include +# ifdef __cplusplus +extern "C" +# endif +void *alloca (size_t); +#endif + +#include "eina_simple_xml_parser.h" + +#include +#include +#include + +#ifdef HAVE_EVIL +# include +#endif + +#include "eina_private.h" +#include "eina_log.h" +#include "eina_mempool.h" +#include "eina_stringshare.h" +#include "eina_strbuf.h" + +/*============================================================================* +* Local * +*============================================================================*/ + +/** + * @cond LOCAL + */ + +static Eina_Mempool *_eina_simple_xml_tag_mp = NULL; +static Eina_Mempool *_eina_simple_xml_attribute_mp = NULL; +static int _eina_simple_xml_log_dom = -1; + +static const char EINA_MAGIC_SIMPLE_XML_TAG_STR[] = "Eina Simple XML Tag"; +static const char EINA_MAGIC_SIMPLE_XML_DATA_STR[] = "Eina Simple XML Data"; +static const char EINA_MAGIC_SIMPLE_XML_ATTRIBUTE_STR[] = "Eina Simple XML Attribute"; + +#define EINA_MAGIC_CHECK_TAG(d, ...) \ + do { \ + if (!EINA_MAGIC_CHECK(d, EINA_MAGIC_SIMPLE_XML_TAG)) \ + { \ + EINA_MAGIC_FAIL(d, EINA_MAGIC_SIMPLE_XML_TAG); \ + return __VA_ARGS__; \ + } \ + } while(0) + +#define EINA_MAGIC_CHECK_DATA(d, ...) \ + do { \ + if (!EINA_MAGIC_CHECK(d, EINA_MAGIC_SIMPLE_XML_DATA)) \ + { \ + EINA_MAGIC_FAIL(d, EINA_MAGIC_SIMPLE_XML_DATA); \ + return __VA_ARGS__; \ + } \ + } while(0) + +#define EINA_MAGIC_CHECK_ATTRIBUTE(d, ...) \ + do { \ + if (!EINA_MAGIC_CHECK(d, EINA_MAGIC_SIMPLE_XML_ATTRIBUTE)) \ + { \ + EINA_MAGIC_FAIL(d, EINA_MAGIC_SIMPLE_XML_ATTRIBUTE); \ + return __VA_ARGS__; \ + } \ + } while(0) + + +#ifndef EINA_LOG_COLOR_DEFAULT +#define EINA_LOG_COLOR_DEFAULT EINA_COLOR_CYAN +#endif + +#ifdef ERR +#undef ERR +#endif +#define ERR(...) EINA_LOG_DOM_ERR(_eina_simple_xml_log_dom, __VA_ARGS__) + +#ifdef WRN +#undef WRN +#endif +#define WRN(...) EINA_LOG_DOM_WARN(_eina_simple_xml_log_dom, __VA_ARGS__) + +#ifdef DBG +#undef DBG +#endif +#define DBG(...) EINA_LOG_DOM_DBG(_eina_simple_xml_log_dom, __VA_ARGS__) + + +static inline const char * +_eina_simple_xml_whitespace_find(const char *itr, const char *itr_end) +{ + for (; itr < itr_end; itr++) + if (isspace(*itr)) break; + return itr; +} + +static inline const char * +_eina_simple_xml_whitespace_skip(const char *itr, const char *itr_end) +{ + for (; itr < itr_end; itr++) + if (!isspace(*itr)) break; + return itr; +} + +static inline const char * +_eina_simple_xml_whitespace_unskip(const char *itr, const char *itr_start) +{ + for (itr--; itr > itr_start; itr--) + if (!isspace(*itr)) break; + return itr + 1; +} + +static inline const char * +_eina_simple_xml_tag_start_find(const char *itr, const char *itr_end) +{ + return memchr(itr, '<', itr_end - itr); +} + +static inline const char * +_eina_simple_xml_tag_end_find(const char *itr, const char *itr_end) +{ + for (; itr < itr_end; itr++) + if ((*itr == '>') || (*itr == '<')) /* consider < also ends a tag */ + return itr; + return NULL; +} + +/** + * @endcond + */ + +/*============================================================================* +* Global * +*============================================================================*/ + + +/** + * @internal + * @brief Initialize the simple xml parser module. + * + * @return #EINA_TRUE on success, #EINA_FALSE on failure. + * + * This function sets up the simple xml parser module of Eina. It is called by + * eina_init(). + * + * @see eina_init() + */ +Eina_Bool +eina_simple_xml_init(void) +{ + const char *choice, *tmp; + + _eina_simple_xml_log_dom = eina_log_domain_register("eina_simple_xml", + EINA_LOG_COLOR_DEFAULT); + if (_eina_simple_xml_log_dom < 0) + { + EINA_LOG_ERR("Could not register log domain: eina_simple_xml"); + return EINA_FALSE; + } + +#ifdef EINA_DEFAULT_MEMPOOL + choice = "pass_through"; +#else + choice = "chained_mempool"; +#endif + tmp = getenv("EINA_MEMPOOL"); + if (tmp && tmp[0]) + choice = tmp; + + _eina_simple_xml_tag_mp = eina_mempool_add + (choice, "simple_xml_tag", NULL, + sizeof(Eina_Simple_XML_Node_Tag), 320); + if (!_eina_simple_xml_tag_mp) + { + ERR("Mempool for simple_xml_tag cannot be allocated in init."); + goto on_init_fail; + } + + _eina_simple_xml_attribute_mp = eina_mempool_add + (choice, "simple_xml_attribute", NULL, + sizeof(Eina_Simple_XML_Attribute), 80); + if (!_eina_simple_xml_attribute_mp) + { + ERR("Mempool for simple_xml_attribute cannot be allocated in init."); + eina_mempool_del(_eina_simple_xml_tag_mp); + goto on_init_fail; + } + +#define EMS(n) eina_magic_string_static_set(n, n ## _STR) + EMS(EINA_MAGIC_SIMPLE_XML_TAG); + EMS(EINA_MAGIC_SIMPLE_XML_DATA); + EMS(EINA_MAGIC_SIMPLE_XML_ATTRIBUTE); +#undef EMS + + return EINA_TRUE; + +on_init_fail: + eina_log_domain_unregister(_eina_simple_xml_log_dom); + _eina_simple_xml_log_dom = -1; + return EINA_FALSE; +} + +/** + * @internal + * @brief Shut down the simple xml parser module. + * + * @return #EINA_TRUE on success, #EINA_FALSE on failure. + * + * This function shuts down the simple xml parser module set + * up by eina_simple_xml_init(). It is called by + * eina_shutdown(). + * + * @see eina_shutdown() + */ +Eina_Bool +eina_simple_xml_shutdown(void) +{ + eina_mempool_del(_eina_simple_xml_attribute_mp); + eina_mempool_del(_eina_simple_xml_tag_mp); + + eina_log_domain_unregister(_eina_simple_xml_log_dom); + _eina_simple_xml_log_dom = -1; + return EINA_TRUE; +} + +/** + * @defgroup Eina_Simple_XML_Group Simple_XML + * + * Simplistic relaxed SAX-like XML parser. + * + * This parser is far from being compliant with XML standard, but will + * do for most XMLs out there. If you know that your format is simple + * and will not vary in future with strange corner cases, then you can + * use it safely. + * + * The parser is SAX like, that is, it will tokenize contents and call + * you back so you can take some action. No contents are allocated + * during this parser work and it's not recursive, so you can use it + * with a very large document without worries. + * + * It will not validate the document anyhow, neither it will create a + * tree hierarchy. That's up to you. + * + * Accordingly to XML, open tags may contain attributes. This parser + * will not tokenize this. If you want you can use + * eina_simple_xml_tag_attributes_find() and then + * eina_simple_xml_attributes_parse(). + * + * @{ + */ + +/* + * @param buf the input string. May not contain \0 terminator. + * @param buflen the input string size. + * @param strip whenever this parser should strip leading and trailing + * whitespace. These whitespace will still be issued, but as type + * #EINA_SIMPLE_XML_IGNORED. + * @param func what to call back while parse to do some action. The + * first parameter is the given user @a data, the second is the + * token type, the third is the pointer to content start (it's + * not a NULL terminated string!), the forth is where this + * content is located inside @a buf (does not include tag + * start, for instance "" the offset points at + * "value"), the fifth is the size of the content. Whenver this + * function return EINA_FALSE the parser will abort. @param + * data what to give as context to @a func. + * + * @return EINA_TRUE on success or EINA_FALSE if it was aborted by user or + * parsing error. + */ +EAPI Eina_Bool +eina_simple_xml_parse(const char *buf, unsigned buflen, Eina_Bool strip, Eina_Simple_XML_Cb func, const void *data) +{ + const char *itr = buf, *itr_end = buf + buflen; + + if (!buf) return EINA_FALSE; + if (!func) return EINA_FALSE; + +#define CB(type, start, end) \ + do \ + { \ + size_t _sz = end - start; \ + Eina_Bool _ret; \ + _ret = func((void*)data, type, start, start - buf, _sz); \ + if (!_ret) return EINA_FALSE; \ + } \ + while (0) + + while (itr < itr_end) + { + if (itr[0] == '<') + { + if (itr + 1 >= itr_end) + { + CB(EINA_SIMPLE_XML_ERROR, itr, itr_end); + return EINA_FALSE; + } + else + { + Eina_Simple_XML_Type type; + size_t toff; + const char *p; + + if (itr[1] == '/') + { + type = EINA_SIMPLE_XML_CLOSE; + toff = 1; + } + else if (itr[1] == '?') + { + type = EINA_SIMPLE_XML_PROCESSING; + toff = 1; + } + else if (itr[1] == '!') + { + if ((itr + sizeof("") - 1 < itr_end) && + (!memcmp(itr + 2, "DOCTYPE", + sizeof("DOCTYPE") - 1)) && + ((itr[2 + sizeof("DOCTYPE") - 1] == '>') || + (isspace(itr[2 + sizeof("DOCTYPE") - 1])))) + { + type = EINA_SIMPLE_XML_DOCTYPE; + toff = sizeof("!DOCTYPE") - 1; + } + else if ((itr + sizeof("") - 1 < itr_end) && + (!memcmp(itr + 2, "--", sizeof("--") - 1))) + { + type = EINA_SIMPLE_XML_COMMENT; + toff = sizeof("!--") - 1; + } + else if ((itr + sizeof("") - 1 < itr_end) && + (!memcmp(itr + 2, "[CDATA[", + sizeof("[CDATA[") - 1))) + { + type = EINA_SIMPLE_XML_CDATA; + toff = sizeof("![CDATA[") - 1; + } + else + { + type = EINA_SIMPLE_XML_OPEN; + toff = 0; + } + } + else + { + type = EINA_SIMPLE_XML_OPEN; + toff = 0; + } + + p = _eina_simple_xml_tag_end_find(itr + 1 + toff, itr_end); + if (p) + { + if (type == EINA_SIMPLE_XML_CDATA) + { + /* must end with ]]> */ + while ((p) && (memcmp(p - 2, "]]>", 3))) + p = _eina_simple_xml_tag_end_find(p + 1, itr_end); + } + + if (*p == '<') + { + type = EINA_SIMPLE_XML_ERROR; + toff = 0; + } + } + + if (p) + { + const char *start, *end; + + start = itr + 1 + toff; + end = p; + + switch (type) + { + case EINA_SIMPLE_XML_OPEN: + if (p[-1] == '/') + { + type = EINA_SIMPLE_XML_OPEN_EMPTY; + end--; + } + break; + case EINA_SIMPLE_XML_CDATA: + if (!memcmp(p - 2, "]]", 2)) end -= 2; + break; + case EINA_SIMPLE_XML_PROCESSING: + if (p[-1] == '?') end--; + break; + case EINA_SIMPLE_XML_COMMENT: + if (!memcmp(p - 2, "--", 2)) end -= 2; + break; + case EINA_SIMPLE_XML_OPEN_EMPTY: + case EINA_SIMPLE_XML_CLOSE: + case EINA_SIMPLE_XML_DATA: + case EINA_SIMPLE_XML_ERROR: + case EINA_SIMPLE_XML_DOCTYPE: + case EINA_SIMPLE_XML_IGNORED: + break; + } + + if ((strip) && (type != EINA_SIMPLE_XML_ERROR)) + { + start = _eina_simple_xml_whitespace_skip + (start, end); + end = _eina_simple_xml_whitespace_unskip + (end, start + 1); + } + + CB(type, start, end); + + if (type != EINA_SIMPLE_XML_ERROR) + itr = p + 1; + else + itr = p; + } + else + { + CB(EINA_SIMPLE_XML_ERROR, itr, itr_end); + return EINA_FALSE; + } + } + } + else + { + const char *p, *end; + + if (strip) + { + p = _eina_simple_xml_whitespace_skip(itr, itr_end); + if (p) + { + CB(EINA_SIMPLE_XML_IGNORED, itr, p); + itr = p; + } + } + + p = _eina_simple_xml_tag_start_find(itr, itr_end); + if (!p) p = itr_end; + + end = p; + if (strip) + end = _eina_simple_xml_whitespace_unskip(end, itr); + + if (itr != end) + CB(EINA_SIMPLE_XML_DATA, itr, end); + + if ((strip) && (end < p)) + CB(EINA_SIMPLE_XML_IGNORED, end, p); + + itr = p; + } + } + +#undef CB + + return EINA_TRUE; +} + +/** + * Given the contents of a tag, find where the attributes start. + * + * The tag contents is returned by eina_simple_xml_parse() when + * type is #EINA_SIMPLE_XML_OPEN or #EINA_SIMPLE_XML_OPEN_EMPTY. + * + * @return pointer to the start of attributes, it can be used + * to feed eina_simple_xml_attributes_parse(). NULL is returned + * if no attributes were found. + */ +EAPI const char * +eina_simple_xml_tag_attributes_find(const char *buf, unsigned buflen) +{ + const char *itr = buf, *itr_end = buf + buflen; + + for (; itr < itr_end; itr++) + { + if (!isspace(*itr)) + { + /* user skip tagname and already gave it the attributes */ + if (*itr == '=') + return buf; + } + else + { + itr = _eina_simple_xml_whitespace_skip(itr + 1, itr_end); + if (itr == itr_end) + return NULL; + return itr; + } + } + + return NULL; +} + +/** + * Given a buffer with xml attributes, parse them to key=value pairs. + * + * @param buf the input string. May not contain \0 terminator. + * @param buflen the input string size. + * @param func what to call back while parse to do some action. The + * first parameter is the given user @a data, the second is the + * key (null-terminated) and the last is the value (null + * terminated). These strings should not be modified and + * reference is just valid until the function return. + * + * @return EINA_TRUE on success or EINA_FALSE if it was aborted by user or + * parsing error. + */ +EAPI Eina_Bool +eina_simple_xml_attributes_parse(const char *buf, unsigned buflen, Eina_Simple_XML_Attribute_Cb func, const void *data) +{ + const char *itr = buf, *itr_end = buf + buflen; + char *tmpbuf = alloca(buflen + 1); + + if (!buf) return EINA_FALSE; + if (!func) return EINA_FALSE; + + while (itr < itr_end) + { + const char *p = _eina_simple_xml_whitespace_skip(itr, itr_end); + const char *key, *key_end, *value, *value_end; + char *tval; + + if (p == itr_end) return EINA_TRUE; + + key = p; + for (key_end = key; key_end < itr_end; key_end++) + if ((*key_end == '=') || (isspace(*key_end))) break; + if (key_end == itr_end) return EINA_FALSE; + if (key_end == key) continue; + + if (*key_end == '=') value = key_end + 1; + else + { + value = memchr(key_end, '=', itr_end - key_end); + if (!value) return EINA_FALSE; + value++; + } + for (; value < itr_end; value++) + if (!isspace(*value)) break; + if (value == itr_end) return EINA_FALSE; + + if ((*value == '"') || (*value == '\'')) + { + value_end = memchr(value + 1, *value, itr_end - value); + if (!value_end) return EINA_FALSE; + value++; + } + else + { + value_end = _eina_simple_xml_whitespace_find(value, itr_end); + } + + memcpy(tmpbuf, key, key_end - key); + tmpbuf[key_end - key] = '\0'; + + tval = tmpbuf + (key_end - key) + 1; + memcpy(tval, value, value_end - value); + tval[value_end - value] = '\0'; + + if (!func((void*)data, tmpbuf, tval)) + return EINA_FALSE; + + itr = value_end + 1; + } + return EINA_TRUE; +} + +/* Node loader *************************************************************/ + +/** + * Create (and append) new attribute to tag. + * + * @param parent if provided, will be set in the resulting structure + * as well as the attribute will be appended to attributes list. + * @param key null-terminated string. Must not be NULL. + * @param value null-terminated string. If NULL, the empty string will be used. + * + * @return newly allocated memory or NULL on error. This memory should be + * released with eina_simple_xml_attribute_free() or indirectly + * with eina_simple_xml_node_tag_free(). + */ +EAPI Eina_Simple_XML_Attribute * +eina_simple_xml_attribute_new(Eina_Simple_XML_Node_Tag *parent, const char *key, const char *value) +{ + Eina_Simple_XML_Attribute *attr; + + if (!key) return NULL; + + attr = eina_mempool_malloc(_eina_simple_xml_attribute_mp, sizeof(*attr)); + if (!attr) + { + ERR("could not allocate memory for attribute from mempool"); + return NULL; + } + + EINA_MAGIC_SET(attr, EINA_MAGIC_SIMPLE_XML_ATTRIBUTE); + attr->parent = parent; + attr->key = eina_stringshare_add(key); + attr->value = eina_stringshare_add(value ? value : ""); + + if (parent) + parent->attributes = eina_inlist_append + (parent->attributes, EINA_INLIST_GET(attr)); + + return attr; +} + +/** + * Remove attribute from parent and delete it. + * + * @param attr attribute to release memory. + */ +EAPI void +eina_simple_xml_attribute_free(Eina_Simple_XML_Attribute *attr) +{ + EINA_MAGIC_CHECK_ATTRIBUTE(attr); + + if (attr->parent) + attr->parent->attributes = eina_inlist_remove + (attr->parent->attributes, EINA_INLIST_GET(attr)); + + eina_stringshare_del(attr->key); + eina_stringshare_del(attr->value); + EINA_MAGIC_SET(attr, EINA_MAGIC_NONE); + eina_mempool_free(_eina_simple_xml_attribute_mp, attr); +} + +static void +_eina_simple_xml_node_data_free(Eina_Simple_XML_Node_Data *node) +{ + if (node->base.parent) + node->base.parent->children = eina_inlist_remove + (node->base.parent->children, EINA_INLIST_GET(&node->base)); + + EINA_MAGIC_SET(&node->base, EINA_MAGIC_NONE); + free(node); +} + +/** + * Create new tag. If parent is provided, it is automatically appended. + * + * @param parent if provided, will be set in the resulting structure + * as well as the tag will be appended to children list. + * @param name null-terminated string. Must not be NULL. + * + * @return newly allocated memory or NULL on error. This memory should be + * released with eina_simple_xml_node_tag_free() or indirectly + * with eina_simple_xml_node_tag_free() of the parent. + */ +EAPI Eina_Simple_XML_Node_Tag * +eina_simple_xml_node_tag_new(Eina_Simple_XML_Node_Tag *parent, const char *name) +{ + Eina_Simple_XML_Node_Tag *n; + + if (!name) return NULL; + + n = eina_mempool_malloc(_eina_simple_xml_tag_mp, sizeof(*n)); + if (!n) + { + ERR("could not allocate memory for node from mempool"); + return NULL; + } + + memset(n, 0, sizeof(*n)); + + EINA_MAGIC_SET(&n->base, EINA_MAGIC_SIMPLE_XML_TAG); + + n->base.type = EINA_SIMPLE_XML_NODE_TAG; + n->base.parent = parent; + n->name = eina_stringshare_add(name); + + if (parent) + parent->children = eina_inlist_append + (parent->children, EINA_INLIST_GET(&n->base)); + + return n; +} + +void +_eina_simple_xml_node_tag_free(Eina_Simple_XML_Node_Tag *tag) +{ + while (tag->children) + { + Eina_Simple_XML_Node *n = EINA_INLIST_CONTAINER_GET + (tag->children, Eina_Simple_XML_Node); + if (n->type == EINA_SIMPLE_XML_NODE_TAG) + _eina_simple_xml_node_tag_free((Eina_Simple_XML_Node_Tag *)n); + else + _eina_simple_xml_node_data_free((Eina_Simple_XML_Node_Data *)n); + } + + while (tag->attributes) + { + Eina_Simple_XML_Attribute *a = EINA_INLIST_CONTAINER_GET + (tag->attributes, Eina_Simple_XML_Attribute); + eina_simple_xml_attribute_free(a); + } + + if (tag->base.parent) + tag->base.parent->children = eina_inlist_remove + (tag->base.parent->children, EINA_INLIST_GET(&tag->base)); + + eina_stringshare_del(tag->name); + EINA_MAGIC_SET(&tag->base, EINA_MAGIC_NONE); + eina_mempool_free(_eina_simple_xml_tag_mp, tag); +} + +/** + * Remove tag from parent and delete it. + * + * @param tag to release memory. + */ +EAPI void +eina_simple_xml_node_tag_free(Eina_Simple_XML_Node_Tag *tag) +{ + EINA_MAGIC_CHECK_TAG(&tag->base); + if (tag->base.type != EINA_SIMPLE_XML_NODE_TAG) + { + ERR("expected tag node!"); + return; + } + _eina_simple_xml_node_tag_free(tag); +} + +static Eina_Simple_XML_Node_Data * +_eina_simple_xml_node_data_new(Eina_Simple_XML_Node_Tag *parent, Eina_Simple_XML_Node_Type type, const char *content, unsigned length) +{ + Eina_Simple_XML_Node_Data *n = malloc(sizeof(*n) + length + 1); + + if (!content) return NULL; + + if (!n) + { + ERR("could not allocate memory for node"); + return NULL; + } + + EINA_MAGIC_SET(&n->base, EINA_MAGIC_SIMPLE_XML_DATA); + n->base.type = type; + n->base.parent = parent; + + n->length = length; + memcpy(n->data, content, length); + n->data[length] = '\0'; + + if (parent) + parent->children = eina_inlist_append + (parent->children, EINA_INLIST_GET(&n->base)); + + return n; +} + +/** + * Create new data. If parent is provided, it is automatically appended. + * + * @param parent if provided, will be set in the resulting structure + * as well as the data will be appended to children list. + * @param content string to be used. Must not be NULL. + * @param length size in bytes of @a content. + * + * @return newly allocated memory or NULL on error. This memory should be + * released with eina_simple_xml_node_data_free() or indirectly + * with eina_simple_xml_node_tag_free() of the parent. + */ +EAPI Eina_Simple_XML_Node_Data * +eina_simple_xml_node_data_new(Eina_Simple_XML_Node_Tag *parent, const char *contents, size_t length) +{ + return _eina_simple_xml_node_data_new + (parent, EINA_SIMPLE_XML_NODE_DATA, contents, length); +} + +/** + * Remove data from parent and delete it. + * + * @param data to release memory. + */ +EAPI void +eina_simple_xml_node_data_free(Eina_Simple_XML_Node_Data *node) +{ + EINA_MAGIC_CHECK_DATA(&node->base); + if (node->base.type != EINA_SIMPLE_XML_NODE_DATA) + { + ERR("expected node of type: data!"); + return; + } + _eina_simple_xml_node_data_free(node); +} + +/** + * Create new cdata. If parent is provided, it is automatically appended. + * + * @param parent if provided, will be set in the resulting structure + * as well as the cdata will be appended to children list. + * @param content string to be used. Must not be NULL. + * @param length size in bytes of @a content. + * + * @return newly allocated memory or NULL on error. This memory should be + * released with eina_simple_xml_node_cdata_free() or indirectly + * with eina_simple_xml_node_tag_free() of the parent. + */ +EAPI Eina_Simple_XML_Node_CData * +eina_simple_xml_node_cdata_new(Eina_Simple_XML_Node_Tag *parent, const char *contents, size_t length) +{ + return _eina_simple_xml_node_data_new + (parent, EINA_SIMPLE_XML_NODE_CDATA, contents, length); +} + +/** + * Remove cdata from parent and delete it. + * + * @param cdata to release memory. + */ +EAPI void +eina_simple_xml_node_cdata_free(Eina_Simple_XML_Node_Data *node) +{ + EINA_MAGIC_CHECK_DATA(&node->base); + if (node->base.type != EINA_SIMPLE_XML_NODE_CDATA) + { + ERR("expected node of type: cdata!"); + return; + } + _eina_simple_xml_node_data_free(node); +} + +/** + * Create new processing. If parent is provided, it is automatically appended. + * + * @param parent if provided, will be set in the resulting structure + * as well as the processing will be appended to children list. + * @param content string to be used. Must not be NULL. + * @param length size in bytes of @a content. + * + * @return newly allocated memory or NULL on error. This memory should be + * released with eina_simple_xml_node_processing_free() or indirectly + * with eina_simple_xml_node_tag_free() of the parent. + */ +EAPI Eina_Simple_XML_Node_Processing * +eina_simple_xml_node_processing_new(Eina_Simple_XML_Node_Tag *parent, const char *contents, size_t length) +{ + return _eina_simple_xml_node_data_new + (parent, EINA_SIMPLE_XML_NODE_PROCESSING, contents, length); +} + +/** + * Remove processing from parent and delete it. + * + * @param processing to release memory. + */ +EAPI void +eina_simple_xml_node_processing_free(Eina_Simple_XML_Node_Data *node) +{ + EINA_MAGIC_CHECK_DATA(&node->base); + if (node->base.type != EINA_SIMPLE_XML_NODE_PROCESSING) + { + ERR("expected node of type: processing!"); + return; + } + _eina_simple_xml_node_data_free(node); +} + +/** + * Create new doctype. If parent is provided, it is automatically appended. + * + * @param parent if provided, will be set in the resulting structure + * as well as the doctype will be appended to children list. + * @param content string to be used. Must not be NULL. + * @param length size in bytes of @a content. + * + * @return newly allocated memory or NULL on error. This memory should be + * released with eina_simple_xml_node_doctype_free() or indirectly + * with eina_simple_xml_node_tag_free() of the parent. + */ +EAPI Eina_Simple_XML_Node_Doctype * +eina_simple_xml_node_doctype_new(Eina_Simple_XML_Node_Tag *parent, const char *contents, size_t length) +{ + return _eina_simple_xml_node_data_new + (parent, EINA_SIMPLE_XML_NODE_DOCTYPE, contents, length); +} + +/** + * Remove doctype from parent and delete it. + * + * @param doctype to release memory. + */ +EAPI void +eina_simple_xml_node_doctype_free(Eina_Simple_XML_Node_Data *node) +{ + EINA_MAGIC_CHECK_DATA(&node->base); + if (node->base.type != EINA_SIMPLE_XML_NODE_DOCTYPE) + { + ERR("expected node of type: doctype!"); + return; + } + _eina_simple_xml_node_data_free(node); +} + +/** + * Create new comment. If parent is provided, it is automatically appended. + * + * @param parent if provided, will be set in the resulting structure + * as well as the comment will be appended to children list. + * @param content string to be used. Must not be NULL. + * @param length size in bytes of @a content. + * + * @return newly allocated memory or NULL on error. This memory should be + * released with eina_simple_xml_node_comment_free() or indirectly + * with eina_simple_xml_node_tag_free() of the parent. + */ +EAPI Eina_Simple_XML_Node_Comment * +eina_simple_xml_node_comment_new(Eina_Simple_XML_Node_Tag *parent, const char *contents, size_t length) +{ + return _eina_simple_xml_node_data_new + (parent, EINA_SIMPLE_XML_NODE_COMMENT, contents, length); +} + +/** + * Remove comment from parent and delete it. + * + * @param comment to release memory. + */ +EAPI void +eina_simple_xml_node_comment_free(Eina_Simple_XML_Node_Data *node) +{ + EINA_MAGIC_CHECK_DATA(&node->base); + if (node->base.type != EINA_SIMPLE_XML_NODE_COMMENT) + { + ERR("expected node of type: comment!"); + return; + } + _eina_simple_xml_node_data_free(node); +} + +struct eina_simple_xml_node_load_ctxt +{ + Eina_Simple_XML_Node_Root *root; + Eina_Simple_XML_Node_Tag *current; +}; + +static Eina_Bool +_eina_simple_xml_attrs_parse(void *data, const char *key, const char *value) +{ + Eina_Simple_XML_Node_Tag *n = data; + Eina_Simple_XML_Attribute *attr; + + attr = eina_simple_xml_attribute_new(n, key, value); + return !!attr; +} + +static Eina_Bool +_eina_simple_xml_node_parse(void *data, Eina_Simple_XML_Type type, const char *content, unsigned offset, unsigned length) +{ + struct eina_simple_xml_node_load_ctxt *ctx = data; + + switch (type) + { + case EINA_SIMPLE_XML_OPEN: + case EINA_SIMPLE_XML_OPEN_EMPTY: + { + Eina_Simple_XML_Node_Tag *n; + const char *name, *name_end, *attrs; + + attrs = eina_simple_xml_tag_attributes_find(content, length); + if (!attrs) + name_end = content + length; + else + name_end = attrs; + + name_end = _eina_simple_xml_whitespace_unskip(name_end, content); + + name = eina_stringshare_add_length(content, name_end - content); + n = eina_simple_xml_node_tag_new(ctx->current, name); + eina_stringshare_del(name); + if (!n) return EINA_FALSE; + + if (attrs) + eina_simple_xml_attributes_parse + (attrs, length - (attrs - content), + _eina_simple_xml_attrs_parse, n); + + if (type == EINA_SIMPLE_XML_OPEN) + ctx->current = n; + } + break; + + case EINA_SIMPLE_XML_CLOSE: + if (ctx->current->base.parent) + { + const char *end = _eina_simple_xml_whitespace_unskip + (content + length, content); + int len; + len = end - content; + if ((len == 0) /* closes the tag for us. */ || + ((eina_stringshare_strlen(ctx->current->name) == len) && + (memcmp(ctx->current->name, content, len) == 0))) + ctx->current = ctx->current->base.parent; + else + WRN("closed incorrect tag: '%.*s', '%s' was expected!", + len, content, ctx->current->name); + } + else + WRN("closed tag '%.*s' but already at document root!", + length, content); + break; + + case EINA_SIMPLE_XML_DATA: + return !!eina_simple_xml_node_data_new + (ctx->current, content, length); + case EINA_SIMPLE_XML_CDATA: + return !!eina_simple_xml_node_cdata_new + (ctx->current, content, length); + case EINA_SIMPLE_XML_PROCESSING: + return !!eina_simple_xml_node_processing_new + (ctx->current, content, length); + case EINA_SIMPLE_XML_DOCTYPE: + return !!eina_simple_xml_node_doctype_new + (ctx->current, content, length); + case EINA_SIMPLE_XML_COMMENT: + return !!eina_simple_xml_node_comment_new + (ctx->current, content, length); + + case EINA_SIMPLE_XML_ERROR: + ERR("parser error at offset %u-%u: %.*s", + offset, length, length, content); + break; + case EINA_SIMPLE_XML_IGNORED: + DBG("ignored contents at offset %u-%u: %.*s", + offset, length, length, content); + break; + } + + return EINA_TRUE; +} + +/** + * Load a XML node tree based on the given string. + * + * @param buf the input string. May not contain \0 terminator. + * @param buflen the input string size. + * @param strip whenever this parser should strip leading and trailing + * whitespace. + * + * @return document root with children tags, or NULL on errors. + * Document with errors may return partial tree instead of NULL, + * we'll do our best to avoid returning nothing. + */ +EAPI Eina_Simple_XML_Node_Root * +eina_simple_xml_node_load(const char *buf, unsigned buflen, Eina_Bool strip) +{ + Eina_Simple_XML_Node_Root *root; + struct eina_simple_xml_node_load_ctxt ctx; + + if (!buf) return NULL; + + root = eina_mempool_malloc(_eina_simple_xml_tag_mp, sizeof(*root)); + if (!root) return NULL; + + memset(root, 0, sizeof(*root)); + EINA_MAGIC_SET(&root->base, EINA_MAGIC_SIMPLE_XML_TAG); + root->base.type = EINA_SIMPLE_XML_NODE_ROOT; + + ctx.root = root; + ctx.current = root; + eina_simple_xml_parse(buf, buflen, strip, _eina_simple_xml_node_parse, &ctx); + + return root; +} + +/** + * Free node tree build with eina_simple_xml_node_load() + * + * @param root memory returned by eina_simple_xml_node_load() + */ +EAPI void +eina_simple_xml_node_root_free(Eina_Simple_XML_Node_Root *root) +{ + if (!root) return; + EINA_MAGIC_CHECK_TAG(&root->base); + if (root->base.type != EINA_SIMPLE_XML_NODE_ROOT) + { + ERR("expected root node!"); + return; + } + _eina_simple_xml_node_tag_free(root); +} + +static inline void +_eina_simple_xml_node_dump_indent(Eina_Strbuf *buf, const char *indent, unsigned level) +{ + unsigned i, indent_len = strlen(indent); + for (i = 0; i < level; i++) + eina_strbuf_append_length(buf, indent, indent_len); +} + +static void +_eina_simple_xml_node_tag_attributes_append(Eina_Strbuf *buf, Eina_Simple_XML_Node_Tag *tag) +{ + Eina_Simple_XML_Attribute *a; + + EINA_INLIST_FOREACH(tag->attributes, a) + eina_strbuf_append_printf(buf, " %s=\"%s\"", a->key, a->value); +} + +static void _eina_simple_xml_node_dump(Eina_Strbuf *buf, Eina_Simple_XML_Node *node, const char *indent, unsigned level); + +static void +_eina_simple_xml_node_children_dump(Eina_Strbuf *buf, Eina_Simple_XML_Node_Tag *tag, const char *indent, unsigned level) +{ + Eina_Simple_XML_Node *node; + + EINA_INLIST_FOREACH(tag->children, node) + _eina_simple_xml_node_dump(buf, node, indent, level); +} + +static void +_eina_simple_xml_node_dump(Eina_Strbuf *buf, Eina_Simple_XML_Node *node, const char *indent, unsigned level) +{ + switch (node->type) + { + case EINA_SIMPLE_XML_NODE_ROOT: + _eina_simple_xml_node_children_dump + (buf, (Eina_Simple_XML_Node_Tag *)node, indent, level); + break; + + case EINA_SIMPLE_XML_NODE_TAG: + { + Eina_Simple_XML_Node_Tag *n = (Eina_Simple_XML_Node_Tag *)node; + + if (indent) _eina_simple_xml_node_dump_indent(buf, indent, level); + + eina_strbuf_append_char(buf, '<'); + eina_strbuf_append_length + (buf, n->name, eina_stringshare_strlen(n->name)); + + if (n->attributes) + _eina_simple_xml_node_tag_attributes_append(buf, n); + + if (n->children) + eina_strbuf_append_char(buf, '>'); + else + eina_strbuf_append_length(buf, "/>", sizeof("/>") - 1); + + if (indent) eina_strbuf_append_char(buf, '\n'); + + if (n->children) + { + _eina_simple_xml_node_children_dump(buf, n, indent, level + 1); + + if (indent) + _eina_simple_xml_node_dump_indent(buf, indent, level); + + eina_strbuf_append_length(buf, "name, eina_stringshare_strlen(n->name)); + eina_strbuf_append_char(buf, '>'); + + if (indent) eina_strbuf_append_char(buf, '\n'); + } + } + break; + case EINA_SIMPLE_XML_NODE_DATA: + { + Eina_Simple_XML_Node_Data *n = (Eina_Simple_XML_Node_Data *)node; + + if (indent) _eina_simple_xml_node_dump_indent(buf, indent, level); + eina_strbuf_append_length(buf, n->data, n->length); + if (indent) eina_strbuf_append_char(buf, '\n'); + } + break; + + case EINA_SIMPLE_XML_NODE_CDATA: + { + Eina_Simple_XML_Node_Data *n = (Eina_Simple_XML_Node_Data *)node; + + if (indent) _eina_simple_xml_node_dump_indent(buf, indent, level); + eina_strbuf_append_length(buf, "data, n->length); + eina_strbuf_append_length(buf, "]]>", sizeof("]]>") - 1); + if (indent) eina_strbuf_append_char(buf, '\n'); + } + break; + + case EINA_SIMPLE_XML_NODE_PROCESSING: + { + Eina_Simple_XML_Node_Data *n = (Eina_Simple_XML_Node_Data *)node; + + if (indent) _eina_simple_xml_node_dump_indent(buf, indent, level); + eina_strbuf_append_length(buf, "data, n->length); + eina_strbuf_append_length(buf, " ?>", sizeof(" ?>") - 1); + if (indent) eina_strbuf_append_char(buf, '\n'); + } + break; + + case EINA_SIMPLE_XML_NODE_DOCTYPE: + { + Eina_Simple_XML_Node_Data *n = (Eina_Simple_XML_Node_Data *)node; + + if (indent) _eina_simple_xml_node_dump_indent(buf, indent, level); + eina_strbuf_append_length + (buf, "data, n->length); + eina_strbuf_append_char(buf, '>'); + if (indent) eina_strbuf_append_char(buf, '\n'); + } + break; + + case EINA_SIMPLE_XML_NODE_COMMENT: + { + Eina_Simple_XML_Node_Data *n = (Eina_Simple_XML_Node_Data *)node; + + if (indent) _eina_simple_xml_node_dump_indent(buf, indent, level); + eina_strbuf_append_length(buf, "", sizeof(" -->") - 1); + if (indent) eina_strbuf_append_char(buf, '\n'); + } + break; + } +} + +/** + * Converts the node tree under the given element to a XML string. + * + * @param node the base node to convert. + * @param indent indentation string, or NULL to disable it. + * + * @param NULL on errors or a newly allocated string on success. + */ +EAPI char * +eina_simple_xml_node_dump(Eina_Simple_XML_Node *node, const char *indent) +{ + Eina_Strbuf *buf; + char *ret; + + if (!node) return NULL; + + buf = eina_strbuf_new(); + if (!buf) return NULL; + + _eina_simple_xml_node_dump(buf, node, indent, 0); + + ret = eina_strbuf_string_steal(buf); + eina_strbuf_free(buf); + return ret; +} + +/** + * @} + */