[misc] Replace mxml with libxml2

mxml 4 is binary and source incompatible with 3, and there is no easy way to
stay compatible with both. Not great for a library. So replace with libxml2,
hopefully that is more stable. Also means we can get rid of all the mxml hacks
This commit is contained in:
ejurgensen 2024-05-29 16:37:30 +02:00
parent 75fe3f100a
commit 75b8f06e25
4 changed files with 186 additions and 322 deletions

View File

@ -120,13 +120,7 @@ OWNTONE_MODULES_CHECK([OWNTONE], [ZLIB], [zlib], [deflate], [zlib.h])
OWNTONE_MODULES_CHECK([OWNTONE], [CONFUSE], [libconfuse >= 3.0], [cfg_init], [confuse.h]) OWNTONE_MODULES_CHECK([OWNTONE], [CONFUSE], [libconfuse >= 3.0], [cfg_init], [confuse.h])
OWNTONE_MODULES_CHECK([OWNTONE], [LIBCURL], [libcurl], [curl_global_init], [curl/curl.h]) OWNTONE_MODULES_CHECK([OWNTONE], [LIBCURL], [libcurl], [curl_global_init], [curl/curl.h])
OWNTONE_MODULES_CHECK([OWNTONE], [LIBSODIUM], [libsodium], [sodium_init], [sodium.h]) OWNTONE_MODULES_CHECK([OWNTONE], [LIBSODIUM], [libsodium], [sodium_init], [sodium.h])
OWNTONE_MODULES_CHECK([OWNTONE], [LIBXML2], [libxml-2.0], [xmlInitParser], [libxml/parser.h])
OWNTONE_MODULES_CHECK([OWNTONE], [MINIXML], [mxml],
[mxmlNewElement], [mxml.h],
[
dnl See mxml-compat.h
AC_CHECK_FUNCS([mxmlGetOpaque] [mxmlGetText] [mxmlGetType] [mxmlGetFirstChild])
])
OWNTONE_MODULES_CHECK([COMMON], [SQLITE3], [sqlite3 >= 3.5.0], OWNTONE_MODULES_CHECK([COMMON], [SQLITE3], [sqlite3 >= 3.5.0],
[sqlite3_initialize], [sqlite3.h], [sqlite3_initialize], [sqlite3.h],

View File

@ -40,7 +40,7 @@
#include "parsers/rsp_parser.h" #include "parsers/rsp_parser.h"
#define RSP_VERSION "1.0" #define RSP_VERSION "1.0"
#define RSP_XML_ROOT "?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?" #define RSP_XML_DECLARATION "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>"
#define F_FULL (1 << 0) #define F_FULL (1 << 0)
#define F_BROWSE (1 << 1) #define F_BROWSE (1 << 1)
@ -124,7 +124,7 @@ xml_to_evbuf(struct evbuffer *evbuf, xml_node *tree)
char *xml; char *xml;
int ret; int ret;
xml = xml_to_string(tree); xml = xml_to_string(tree, RSP_XML_DECLARATION);
if (!xml) if (!xml)
{ {
DPRINTF(E_LOG, L_RSP, "Could not finalize RSP reply\n"); DPRINTF(E_LOG, L_RSP, "Could not finalize RSP reply\n");
@ -142,44 +142,53 @@ xml_to_evbuf(struct evbuffer *evbuf, xml_node *tree)
return 0; return 0;
} }
static void static int
rsp_xml_response_new(xml_node **xml_ptr, xml_node **response_ptr, int errorcode, const char *errorstring, int records, int totalrecords) rsp_xml_response_new(xml_node **response_ptr, int errorcode, const char *errorstring, int records, int totalrecords)
{ {
xml_node *xml = xml_new_node(NULL, RSP_XML_ROOT, NULL); xml_node *node;
xml_node *response = xml_new_node(xml, "response", NULL); xml_node *response = xml_new_node(NULL, "response", NULL);
xml_node *status = xml_new_node(response, "status", NULL); xml_node *status = xml_new_node(response, "status", NULL);
if (!response || !status)
return -1;
xml_new_node_textf(status, "errorcode", "%d", errorcode); xml_new_node_textf(status, "errorcode", "%d", errorcode);
xml_new_node(status, "errorstring", errorstring); node = xml_new_node(status, "errorstring", errorstring);
if (errorstring && *errorstring == '\0')
xml_new_text(node, ""); // Prevents sending <errorstring/> which the Soundbridge may not understand
xml_new_node_textf(status, "records", "%d", records); xml_new_node_textf(status, "records", "%d", records);
xml_new_node_textf(status, "totalrecords", "%d", totalrecords); xml_new_node_textf(status, "totalrecords", "%d", totalrecords);
if (response_ptr) if (response_ptr)
*response_ptr = response; *response_ptr = response;
if (xml_ptr)
*xml_ptr = xml; return 0;
} }
static void static void
rsp_send_error(struct httpd_request *hreq, char *errmsg) rsp_send_error(struct httpd_request *hreq, char *errmsg)
{ {
xml_node *xml; xml_node *response = NULL;
int ret; int ret;
rsp_xml_response_new(&xml, NULL, 1, errmsg, 0, 0); CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 1, errmsg, 0, 0));
ret = xml_to_evbuf(hreq->out_body, xml);
xml_free(xml);
ret = xml_to_evbuf(hreq->out_body, response);
if (ret < 0) if (ret < 0)
{ goto error;
httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error");
return;
}
httpd_header_add(hreq->out_headers, "Content-Type", "text/xml; charset=utf-8"); httpd_header_add(hreq->out_headers, "Content-Type", "text/xml; charset=utf-8");
httpd_header_add(hreq->out_headers, "Connection", "close"); httpd_header_add(hreq->out_headers, "Connection", "close");
httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP); httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP);
xml_free(response);
return;
error:
httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error");
xml_free(response);
} }
static int static int
@ -305,7 +314,6 @@ rsp_request_authorize(struct httpd_request *hreq)
static int static int
rsp_reply_info(struct httpd_request *hreq) rsp_reply_info(struct httpd_request *hreq)
{ {
xml_node *xml;
xml_node *response; xml_node *response;
xml_node *info; xml_node *info;
cfg_t *lib; cfg_t *lib;
@ -317,7 +325,7 @@ rsp_reply_info(struct httpd_request *hreq)
lib = cfg_getsec(cfg, "library"); lib = cfg_getsec(cfg, "library");
library = cfg_getstr(lib, "name"); library = cfg_getstr(lib, "name");
rsp_xml_response_new(&xml, &response, 0, "", 0, 0); CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 0, "", 0, 0));
info = xml_new_node(response, "info", NULL); info = xml_new_node(response, "info", NULL);
@ -326,7 +334,7 @@ rsp_reply_info(struct httpd_request *hreq)
xml_new_node(info, "server-version", VERSION); xml_new_node(info, "server-version", VERSION);
xml_new_node(info, "name", library); xml_new_node(info, "name", library);
rsp_send_reply(hreq, xml); rsp_send_reply(hreq, response);
return 0; return 0;
} }
@ -336,7 +344,6 @@ rsp_reply_db(struct httpd_request *hreq)
struct query_params qp; struct query_params qp;
struct db_playlist_info dbpli; struct db_playlist_info dbpli;
char **strval; char **strval;
xml_node *xml;
xml_node *response; xml_node *response;
xml_node *pls; xml_node *pls;
xml_node *pl; xml_node *pl;
@ -357,7 +364,7 @@ rsp_reply_db(struct httpd_request *hreq)
return -1; return -1;
} }
rsp_xml_response_new(&xml, &response, 0, "", qp.results, qp.results); CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 0, "", qp.results, qp.results));
pls = xml_new_node(response, "playlists", NULL); pls = xml_new_node(response, "playlists", NULL);
@ -386,7 +393,7 @@ rsp_reply_db(struct httpd_request *hreq)
{ {
DPRINTF(E_LOG, L_RSP, "Error fetching results\n"); DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
xml_free(xml); xml_free(response);
db_query_end(&qp); db_query_end(&qp);
rsp_send_error(hreq, "Error fetching query results"); rsp_send_error(hreq, "Error fetching query results");
return -1; return -1;
@ -394,7 +401,7 @@ rsp_reply_db(struct httpd_request *hreq)
/* HACK /* HACK
* Add a dummy empty string to the playlists element if there is no data * Add a dummy empty string to the playlists element if there is no data
* to return - this prevents mxml from sending out an empty <playlists/> * to return - this prevents us from sending out an empty <playlists/>
* tag that the SoundBridge does not handle. It's hackish, but it works. * tag that the SoundBridge does not handle. It's hackish, but it works.
*/ */
if (qp.results == 0) if (qp.results == 0)
@ -402,7 +409,7 @@ rsp_reply_db(struct httpd_request *hreq)
db_query_end(&qp); db_query_end(&qp);
rsp_send_reply(hreq, xml); rsp_send_reply(hreq, response);
return 0; return 0;
} }
@ -473,7 +480,6 @@ rsp_reply_playlist(struct httpd_request *hreq)
struct query_params qp; struct query_params qp;
const char *param; const char *param;
const char *client_codecs; const char *client_codecs;
xml_node *xml;
xml_node *response; xml_node *response;
xml_node *items; xml_node *items;
int mode; int mode;
@ -538,7 +544,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
if (qp.limit && (records > qp.limit)) if (qp.limit && (records > qp.limit))
records = qp.limit; records = qp.limit;
rsp_xml_response_new(&xml, &response, 0, "", records, qp.results); CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 0, "", records, qp.results));
// Add a parent items block (all items), and then one item per file // Add a parent items block (all items), and then one item per file
items = xml_new_node(response, "items", NULL); items = xml_new_node(response, "items", NULL);
@ -554,7 +560,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
{ {
DPRINTF(E_LOG, L_RSP, "Error fetching results\n"); DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
xml_free(xml); xml_free(response);
db_query_end(&qp); db_query_end(&qp);
rsp_send_error(hreq, "Error fetching query results"); rsp_send_error(hreq, "Error fetching query results");
return -1; return -1;
@ -562,7 +568,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
/* HACK /* HACK
* Add a dummy empty string to the items element if there is no data * Add a dummy empty string to the items element if there is no data
* to return - this prevents mxml from sending out an empty <items/> * to return - this prevents us from sending out an empty <items/>
* tag that the SoundBridge does not handle. It's hackish, but it works. * tag that the SoundBridge does not handle. It's hackish, but it works.
*/ */
if (qp.results == 0) if (qp.results == 0)
@ -570,7 +576,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
db_query_end(&qp); db_query_end(&qp);
rsp_send_reply(hreq, xml); rsp_send_reply(hreq, response);
return 0; return 0;
} }
@ -580,7 +586,6 @@ rsp_reply_browse(struct httpd_request *hreq)
{ {
struct query_params qp; struct query_params qp;
char *browse_item; char *browse_item;
xml_node *xml;
xml_node *response; xml_node *response;
xml_node *items; xml_node *items;
int records; int records;
@ -643,7 +648,7 @@ rsp_reply_browse(struct httpd_request *hreq)
if (qp.limit && (records > qp.limit)) if (qp.limit && (records > qp.limit))
records = qp.limit; records = qp.limit;
rsp_xml_response_new(&xml, &response, 0, "", records, qp.results); CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 0, "", records, qp.results));
items = xml_new_node(response, "items", NULL); items = xml_new_node(response, "items", NULL);
@ -660,7 +665,7 @@ rsp_reply_browse(struct httpd_request *hreq)
{ {
DPRINTF(E_LOG, L_RSP, "Error fetching results\n"); DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
xml_free(xml); xml_free(response);
db_query_end(&qp); db_query_end(&qp);
rsp_send_error(hreq, "Error fetching query results"); rsp_send_error(hreq, "Error fetching query results");
return -1; return -1;
@ -668,7 +673,7 @@ rsp_reply_browse(struct httpd_request *hreq)
/* HACK /* HACK
* Add a dummy empty string to the items element if there is no data * Add a dummy empty string to the items element if there is no data
* to return - this prevents mxml from sending out an empty <items/> * to return - this prevents us from sending out an empty <items/>
* tag that the SoundBridge does not handle. It's hackish, but it works. * tag that the SoundBridge does not handle. It's hackish, but it works.
*/ */
if (qp.results == 0) if (qp.results == 0)
@ -676,7 +681,7 @@ rsp_reply_browse(struct httpd_request *hreq)
db_query_end(&qp); db_query_end(&qp);
rsp_send_reply(hreq, xml); rsp_send_reply(hreq, response);
return 0; return 0;
} }

View File

@ -15,19 +15,6 @@
* along with this program; if not, write to the Free Software * along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* *
*
* About pipe.c
* --------------
* This module will read a PCM16 stream from a named pipe and write it to the
* input buffer. The user may start/stop playback from a pipe by selecting it
* through a client. If the user has configured pipe_autostart, then pipes in
* the library will also be watched for data, and playback will start/stop
* automatically.
*
* The module will also look for pipes with a .metadata suffix, and if found,
* the metadata will be parsed and fed to the player. The metadata must be in
* the format Shairport uses for this purpose.
*
*/ */
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
@ -36,162 +23,18 @@
#include <stdio.h> // fopen #include <stdio.h> // fopen
#include <stdarg.h> // va_* #include <stdarg.h> // va_*
#include <ctype.h> #include <string.h> // strlen
#include <ctype.h> // isspace
#include <mxml.h> #include <libxml/parser.h>
#include <libxml/tree.h>
typedef mxml_node_t xml_node;
/* ---------------- Compability with older versions of mxml ----------------- */
// mxml 2.10 has a memory leak in mxmlDelete, see https://github.com/michaelrsweet/mxml/issues/183
// - and since this is the version in Ubuntu 18.04 LTS and Raspian Stretch, we
// fix it by including a fixed mxmlDelete here. It should be removed once the
// major distros no longer have 2.10. The below code is msweet's fixed mxml.
#if (MXML_MAJOR_VERSION == 2) && (MXML_MINOR_VERSION <= 10)
#define mxmlDelete compat_mxmlDelete
static void
compat_mxml_free(mxml_node_t *node)
{
int i;
switch (node->type)
{
case MXML_ELEMENT :
if (node->value.element.name)
free(node->value.element.name);
if (node->value.element.num_attrs)
{
for (i = 0; i < node->value.element.num_attrs; i ++)
{
if (node->value.element.attrs[i].name)
free(node->value.element.attrs[i].name);
if (node->value.element.attrs[i].value)
free(node->value.element.attrs[i].value);
}
free(node->value.element.attrs);
}
break;
case MXML_INTEGER :
break;
case MXML_OPAQUE :
if (node->value.opaque)
free(node->value.opaque);
break;
case MXML_REAL :
break;
case MXML_TEXT :
if (node->value.text.string)
free(node->value.text.string);
break;
case MXML_CUSTOM :
if (node->value.custom.data &&
node->value.custom.destroy)
(*(node->value.custom.destroy))(node->value.custom.data);
break;
default :
break;
}
free(node);
}
__attribute__((unused)) static void
compat_mxmlDelete(mxml_node_t *node)
{
mxml_node_t *current,
*next;
if (!node)
return;
mxmlRemove(node);
for (current = node->child; current; current = next)
{
if ((next = current->child) != NULL)
{
current->child = NULL;
continue;
}
if ((next = current->next) == NULL)
{
if ((next = current->parent) == node)
next = NULL;
}
compat_mxml_free(current);
}
compat_mxml_free(node);
}
#endif
/* For compability with mxml 2.6 */
#ifndef HAVE_MXMLGETTEXT
__attribute__((unused)) static const char * /* O - Text string or NULL */
mxmlGetText(mxml_node_t *node, /* I - Node to get */
int *whitespace) /* O - 1 if string is preceded by whitespace, 0 otherwise */
{
if (node->type == MXML_TEXT)
return (node->value.text.string);
else if (node->type == MXML_ELEMENT &&
node->child &&
node->child->type == MXML_TEXT)
return (node->child->value.text.string);
else
return (NULL);
}
#endif
#ifndef HAVE_MXMLGETOPAQUE
__attribute__((unused)) static const char * /* O - Opaque string or NULL */
mxmlGetOpaque(mxml_node_t *node) /* I - Node to get */
{
if (!node)
return (NULL);
if (node->type == MXML_OPAQUE)
return (node->value.opaque);
else if (node->type == MXML_ELEMENT &&
node->child &&
node->child->type == MXML_OPAQUE)
return (node->child->value.opaque);
else
return (NULL);
}
#endif
#ifndef HAVE_MXMLGETFIRSTCHILD
__attribute__((unused)) static mxml_node_t * /* O - First child or NULL */
mxmlGetFirstChild(mxml_node_t *node) /* I - Node to get */
{
if (!node || node->type != MXML_ELEMENT)
return (NULL);
return (node->child);
}
#endif
#ifndef HAVE_MXMLGETTYPE
__attribute__((unused)) static mxml_type_t /* O - Type of node */
mxmlGetType(mxml_node_t *node) /* I - Node to get */
{
return (node->type);
}
#endif
typedef xmlNode xml_node;
/* --------------------------------- Helpers -------------------------------- */ /* --------------------------------- Helpers -------------------------------- */
// We get values from mxml via GetOpaque, but that means they can whitespace, static char *
// thus we trim them. A bit dirty, since the values are in principle const. trim(char *str)
static const char *
trim(const char *str)
{ {
char *term; char *term;
@ -205,7 +48,6 @@ trim(const char *str)
while (term != str && isspace(*(term - 1))) while (term != str && isspace(*(term - 1)))
term--; term--;
// Dirty write to the const string from mxml
*term = '\0'; *term = '\0';
return str; return str;
@ -215,9 +57,25 @@ trim(const char *str)
/* -------------------------- Wrapper implementation ------------------------ */ /* -------------------------- Wrapper implementation ------------------------ */
char * char *
xml_to_string(xml_node *top) xml_to_string(xml_node *top, const char *xml_declaration)
{ {
return mxmlSaveAllocString(top, MXML_NO_CALLBACK); xmlBuffer *buf;
char *s;
buf = xmlBufferCreate();
if (!buf)
return NULL;
if (xml_declaration)
xmlBufferWriteChar(buf, xml_declaration);
xmlNodeDump(buf, top->doc, top, 0, 0);
s = strdup((char *)buf->content);
xmlBufferFree(buf);
return s;
} }
// This works both for well-formed xml strings (beginning with <?xml..) and for // This works both for well-formed xml strings (beginning with <?xml..) and for
@ -225,99 +83,90 @@ xml_to_string(xml_node *top)
xml_node * xml_node *
xml_from_string(const char *string) xml_from_string(const char *string)
{ {
mxml_node_t *top; xmlDocPtr doc;
mxml_node_t *node;
top = mxmlNewXML("1.0"); doc = xmlReadMemory(string, strlen(string), NULL, NULL, XML_PARSE_NOBLANKS | XML_PARSE_NOCDATA | XML_PARSE_NONET);
if (!top) if (!doc)
goto error; return NULL;
node = mxmlLoadString(top, string, MXML_OPAQUE_CALLBACK); return xmlDocGetRootElement(doc);
if (!node)
goto error;
return top;
error:
mxmlDelete(top);
return NULL;
} }
xml_node * xml_node *
xml_from_file(const char *path) xml_from_file(const char *path)
{ {
FILE *fp; xmlDocPtr doc;
mxml_node_t *top;
mxml_node_t *node;
top = mxmlNewXML("1.0"); doc = xmlReadFile(path, NULL, XML_PARSE_NOBLANKS | XML_PARSE_NOCDATA | XML_PARSE_NONET);
if (!top) if (!doc)
goto error; return NULL;
fp = fopen(path, "r"); return xmlDocGetRootElement(doc);
node = mxmlLoadFile(top, fp, MXML_OPAQUE_CALLBACK);
fclose(fp);
if (!node)
goto error;
return top;
error:
mxmlDelete(top);
return NULL;
} }
void void
xml_free(xml_node *top) xml_free(xml_node *top)
{ {
mxmlDelete(top); xmlFreeDoc(top->doc);
} }
xml_node * xml_node *
xml_get_node(xml_node *top, const char *path) xml_get_child(xml_node *top, const char *name)
{ {
mxml_node_t *node; xml_node *cur;
mxml_type_t type;
// This example shows why we can't just return the result of mxmlFindPath: for (cur = xmlFirstElementChild(top); cur; cur = xmlNextElementSibling(cur))
// <?xml version="1.0""?><rss> {
// <channel> if (xmlStrEqual(BAD_CAST name, cur->name))
// <title><![CDATA[Tissages]]></title> break;
// mxmlFindPath(top, "rss/channel") will return an OPAQUE node where the }
// opaque value is just the whitespace. What we want is the ELEMENT parent,
// because that's the one we can use to search for children nodes ("title").
node = mxmlFindPath(top, path);
type = mxmlGetType(node);
if (type == MXML_ELEMENT)
return node;
return mxmlGetParent(node); return cur;
} }
xml_node * xml_node *
xml_get_next(xml_node *top, xml_node *node) xml_get_next(xml_node *top, xml_node *node)
{ {
const char *name; return xmlNextElementSibling(node);
const char *s;
name = mxmlGetElement(node);
if (!name)
return NULL;
while ( (node = mxmlGetNextSibling(node)) )
{
s = mxmlGetElement(node);
if (s && strcmp(s, name) == 0)
return node;
}
return NULL;
} }
// Walks through the children of the "path" node until it finds one that is // We don't use xpath because I couldn't figure how to make it search in a node
// not just whitespace and returns a trimmed value (except for CDATA). Means // subtree instead of in the entire xmlDoc + it is more complex than the below.
// that these variations will all give the same result: // If the XML is <foo><bar>value</bar></foo> then both path = "foo/bar" and path
// = "bar" (so a path without the top element) will return "value".
xml_node *
xml_get_node(xml_node *top, const char *path)
{
xml_node *node = top;
char *path_cpy;
char *needle;
char *ptr;
if (!top)
return NULL;
if (!path)
return top;
path_cpy = strdup(path);
needle = strtok_r(path_cpy, "/", &ptr);
if (!needle)
node = NULL;
else if (xmlStrEqual(BAD_CAST needle, node->name))
needle = strtok_r(NULL, "/", &ptr); // Descend one level down the path
while (node && needle)
{
node = xml_get_child(node, needle);
needle = strtok_r(NULL, "/", &ptr);
}
free(path_cpy);
return node;
}
// These variations will all give the same result:
// //
// <foo>FOO FOO</foo><bar>\nBAR BAR \n</bar> // <foo>FOO FOO</foo><bar>\nBAR BAR \n</bar>
// <foo>FOO FOO</foo><bar><![CDATA[BAR BAR]]></bar> // <foo>FOO FOO</foo><bar><![CDATA[BAR BAR]]></bar>
@ -325,50 +174,69 @@ xml_get_next(xml_node *top, xml_node *node)
const char * const char *
xml_get_val(xml_node *top, const char *path) xml_get_val(xml_node *top, const char *path)
{ {
mxml_node_t *parent; xml_node *node;
mxml_node_t *node;
mxml_type_t type;
const char *s = "";
parent = xml_get_node(top, path); node = xml_get_node(top, path);
if (!parent) if (!node || !node->children)
return NULL; return NULL;
for (node = mxmlGetFirstChild(parent); node; node = mxmlGetNextSibling(node)) return trim((char *)node->children->content);
{
type = mxmlGetType(node);
if (type == MXML_OPAQUE)
s = trim(mxmlGetOpaque(node));
else if (type == MXML_ELEMENT)
s = mxmlGetCDATA(node);
if (s && *s != '\0')
break;
}
return s;
} }
const char * const char *
xml_get_attr(xml_node *top, const char *path, const char *name) xml_get_attr(xml_node *top, const char *path, const char *name)
{ {
mxml_node_t *node = mxmlFindPath(top, path); xml_node *node;
xmlAttr *prop;
return mxmlElementGetAttr(node, name); node = xml_get_node(top, path);
if (!node)
return NULL;
prop = xmlHasProp(node, BAD_CAST name);
if (!prop || !prop->children)
return NULL;
return trim((char *)prop->children->content);
}
xml_node *
xml_new(void)
{
xmlDoc *doc;
doc = xmlNewDoc(BAD_CAST "1.0");
if (!doc)
return NULL;
return xmlDocGetRootElement(doc);
} }
xml_node * xml_node *
xml_new_node(xml_node *parent, const char *name, const char *val) xml_new_node(xml_node *parent, const char *name, const char *val)
{ {
if (!parent) xml_node *node;
parent = MXML_NO_PARENT; xmlDoc *doc = NULL;
mxml_node_t *node = mxmlNewElement(parent, name); doc = parent ? parent->doc : xmlNewDoc(BAD_CAST "1.0");
if (!val) if (!doc)
return node; // We're done, caller gets an ELEMENT to use as parent goto error;
node = xmlNewDocNode(doc, NULL, BAD_CAST name, BAD_CAST val);
if (!node)
return NULL;
if (parent)
xmlAddChild(parent, node);
else
xmlDocSetRootElement(doc, node);
mxmlNewText(node, 0, val);
return node; return node;
error:
if (!parent)
xmlFreeDoc(doc);
return NULL;
} }
xml_node * xml_node *
@ -376,7 +244,7 @@ xml_new_node_textf(xml_node *parent, const char *name, const char *format, ...)
{ {
char *s = NULL; char *s = NULL;
va_list va; va_list va;
mxml_node_t *node; xml_node *node;
int ret; int ret;
va_start(va, format); va_start(va, format);
@ -393,8 +261,17 @@ xml_new_node_textf(xml_node *parent, const char *name, const char *format, ...)
return node; return node;
} }
void xml_node *
xml_new_text(xml_node *parent, const char *val) xml_new_text(xml_node *parent, const char *val)
{ {
mxmlNewText(parent, 0, val); xml_node *node;
if (!parent)
return NULL;
node = xmlNewDocText(parent->doc, BAD_CAST val);
if (!node)
return NULL;
return xmlAddChild(parent, node);
} }

View File

@ -1,55 +1,43 @@
#ifndef SRC_MISC_XML_H_ #ifndef SRC_MISC_XML_H_
#define SRC_MISC_XML_H_ #define SRC_MISC_XML_H_
// This wraps mxml and adds some convenience functions. This also means that // This wraps libxml2 and adds some convenience functions
// callers don't need to concern themselves with changes and bugs in various
// versions of mxml.
typedef void xml_node; typedef void xml_node;
// Wraps mxmlSaveAllocString. Returns NULL on error.
char * char *
xml_to_string(xml_node *top); xml_to_string(xml_node *top, const char *xml_declaration);
// Wraps mxmlNewXML and mxmlLoadString, so creates an xml struct with the parsed
// content of string. Returns NULL on error.
xml_node * xml_node *
xml_from_string(const char *string); xml_from_string(const char *string);
// Wraps mxmlNewXML and mxmlLoadFile, so creates an xml struct with the parsed
// content of string. Returns NULL on error.
xml_node * xml_node *
xml_from_file(const char *path); xml_from_file(const char *path);
// Wraps mxmlDelete, which will free node + underlying nodes
void void
xml_free(xml_node *top); xml_free(xml_node *top);
// Wraps mxmlFindPath.
xml_node * xml_node *
xml_get_node(xml_node *top, const char *path); xml_get_node(xml_node *top, const char *path);
// Wraps mxmlGetNextSibling, but only returns sibling nodes that have the same // Only returns sibling nodes that have the same name as input node
// name as input node.
xml_node * xml_node *
xml_get_next(xml_node *top, xml_node *node); xml_get_next(xml_node *top, xml_node *node);
// Wraps mxmlFindPath and mxmlGetOpaque + mxmlGetCDATA. Returns NULL if nothing
// can be found.
const char * const char *
xml_get_val(xml_node *top, const char *path); xml_get_val(xml_node *top, const char *path);
// Wraps mxmlFindPath and mxmlElementGetAttr. Returns NULL if nothing can be
// found.
const char * const char *
xml_get_attr(xml_node *top, const char *path, const char *name); xml_get_attr(xml_node *top, const char *path, const char *name);
// Will create a new XML document with the node as root if parent is NULL
xml_node * xml_node *
xml_new_node(xml_node *parent, const char *name, const char *val); xml_new_node(xml_node *parent, const char *name, const char *val);
xml_node * xml_node *
xml_new_node_textf(xml_node *parent, const char *name, const char *format, ...); xml_new_node_textf(xml_node *parent, const char *name, const char *format, ...);
// Adds a text node to parent, which must be an element node
void void
xml_new_text(xml_node *parent, const char *val); xml_new_text(xml_node *parent, const char *val);