Add alternate string management support.

This commit is contained in:
Michael R Sweet 2024-03-07 14:06:50 -05:00
parent 1dfe8e7ac9
commit 9e22b3ef64
No known key found for this signature in database
GPG Key ID: BE67C75EC81F3244
9 changed files with 292 additions and 104 deletions

View File

@ -9,6 +9,8 @@
`MXML_TYPE_DIRECTIVE` node types (Issue #250)
- Added `mxmlLoadFilename` and `mxmlSaveFilename` functions (Issue #291)
- Added AFL fuzzing support (Issue #306)
- Added string copy/free callbacks to support alternate memory management of
strings.
- Renamed `mxml_type_t` enumerations to `MXML_TYPE_xxx` (Issue #251)
- Updated APIs to use bool type instead of an int representing a boolean value.
- Updated the SAX callback to return a `bool` value to control processing

View File

@ -45,8 +45,8 @@ mxmlElementDeleteAttr(mxml_node_t *node,// I - Element
if (!strcmp(attr->name, name))
{
// Delete this attribute...
free(attr->name);
free(attr->value);
_mxml_strfree(attr->name);
_mxml_strfree(attr->value);
i --;
if (i > 0)
@ -165,7 +165,7 @@ mxmlElementSetAttr(mxml_node_t *node, // I - Element node
if (value)
{
if ((valuec = strdup(value)) == NULL)
if ((valuec = _mxml_strcopy(value)) == NULL)
{
_mxml_error("Unable to allocate memory for attribute '%s' in element %s.", name, node->value.element.name);
return;
@ -177,7 +177,7 @@ mxmlElementSetAttr(mxml_node_t *node, // I - Element node
}
if (!mxml_set_attr(node, name, valuec))
free(valuec);
_mxml_strfree(valuec);
}
@ -212,10 +212,10 @@ mxmlElementSetAttrf(mxml_node_t *node, // I - Element node
vsnprintf(buffer, sizeof(buffer), format, ap);
va_end(ap);
if ((value = strdup(buffer)) == NULL)
if ((value = _mxml_strcopy(buffer)) == NULL)
_mxml_error("Unable to allocate memory for attribute '%s' in element %s.", name, node->value.element.name);
else if (!mxml_set_attr(node, name, value))
free(value);
_mxml_strfree(value);
}
@ -238,7 +238,7 @@ mxml_set_attr(mxml_node_t *node, // I - Element node
if (!strcmp(attr->name, name))
{
// Free the old value as needed...
free(attr->value);
_mxml_strfree(attr->value);
attr->value = value;
return (true);
@ -255,7 +255,7 @@ mxml_set_attr(mxml_node_t *node, // I - Element node
node->value.element.attrs = attr;
attr += node->value.element.num_attrs;
if ((attr->name = strdup(name)) == NULL)
if ((attr->name = _mxml_strcopy(name)) == NULL)
{
_mxml_error("Unable to allocate memory for attribute '%s' in element %s.", name, node->value.element.name);
return (false);

View File

@ -527,50 +527,6 @@ mxmlSaveString(
}
//
// 'mxmlSetCustomHandlers()' - Set the handling functions for custom data.
//
// The load function accepts a node pointer and a data string and must
// return 0 on success and non-zero on error.
//
// The save function accepts a node pointer and must return a malloc'd
// string on success and `NULL` on error.
//
void
mxmlSetCustomCallbacks(
mxml_custom_load_cb_t load_cb, // I - Load callback function
mxml_custom_save_cb_t save_cb, // I - Save callback function
void *cbdata) // I - Callback data
{
_mxml_global_t *global = _mxml_global();
// Global data
global->custom_load_cb = load_cb;
global->custom_save_cb = save_cb;
global->custom_cbdata = cbdata;
}
//
// 'mxmlSetErrorCallback()' - Set the error message callback.
//
void
mxmlSetErrorCallback(
mxml_error_cb_t cb, // I - Error callback function
void *cbdata) // I - Error callback data
{
_mxml_global_t *global = _mxml_global();
// Global data
global->error_cb = cb;
global->error_cbdata = cbdata;
}
//
// 'mxmlSetWrapMargin()' - Set the wrap margin when saving XML data.
//

View File

@ -33,7 +33,7 @@ mxmlIndexDelete(mxml_index_t *ind) // I - Index to delete
return;
// Free memory...
free(ind->attr);
_mxml_strfree(ind->attr);
free(ind->nodes);
free(ind);
}
@ -228,7 +228,7 @@ mxmlIndexNew(mxml_node_t *node, // I - XML node tree
if (attr)
{
if ((ind->attr = strdup(attr)) == NULL)
if ((ind->attr = _mxml_strcopy(attr)) == NULL)
{
_mxml_error("Unable to allocate memory for index attribute.");
free(ind);

View File

@ -207,7 +207,7 @@ mxmlNewCDATA(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT`
// Create the node and set the name value...
if ((node = mxml_new(parent, MXML_TYPE_CDATA)) != NULL)
{
if ((node->value.cdata = strdup(data)) == NULL)
if ((node->value.cdata = _mxml_strcopy(data)) == NULL)
{
_mxml_error("Unable to allocate memory for CDATA.");
mxmlDelete(node);
@ -251,7 +251,7 @@ mxmlNewCDATAf(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT`
vsnprintf(buffer, sizeof(buffer), format, ap);
va_end(ap);
node->value.cdata = strdup(buffer);
node->value.cdata = _mxml_strcopy(buffer);
}
return (node);
@ -283,7 +283,7 @@ mxmlNewComment(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT`
// Create the node and set the name value...
if ((node = mxml_new(parent, MXML_TYPE_COMMENT)) != NULL)
{
if ((node->value.comment = strdup(comment)) == NULL)
if ((node->value.comment = _mxml_strcopy(comment)) == NULL)
{
_mxml_error("Unable to allocate memory for comment.");
mxmlDelete(node);
@ -327,7 +327,7 @@ mxmlNewCommentf(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT`
vsnprintf(buffer, sizeof(buffer), format, ap);
va_end(ap);
node->value.comment = strdup(buffer);
node->value.comment = _mxml_strcopy(buffer);
}
return (node);
@ -391,7 +391,7 @@ mxmlNewDeclaration(
// Create the node and set the name value...
if ((node = mxml_new(parent, MXML_TYPE_DECLARATION)) != NULL)
{
if ((node->value.declaration = strdup(declaration)) == NULL)
if ((node->value.declaration = _mxml_strcopy(declaration)) == NULL)
{
_mxml_error("Unable to allocate memory for declaration.");
mxmlDelete(node);
@ -436,7 +436,7 @@ mxmlNewDeclarationf(
vsnprintf(buffer, sizeof(buffer), format, ap);
va_end(ap);
node->value.declaration = strdup(buffer);
node->value.declaration = _mxml_strcopy(buffer);
}
return (node);
@ -468,7 +468,7 @@ mxmlNewDirective(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT`
// Create the node and set the name value...
if ((node = mxml_new(parent, MXML_TYPE_DIRECTIVE)) != NULL)
{
if ((node->value.directive = strdup(directive)) == NULL)
if ((node->value.directive = _mxml_strcopy(directive)) == NULL)
{
_mxml_error("Unable to allocate memory for processing instruction.");
mxmlDelete(node);
@ -512,7 +512,7 @@ mxmlNewDirectivef(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT`
vsnprintf(buffer, sizeof(buffer), format, ap);
va_end(ap);
node->value.directive = strdup(buffer);
node->value.directive = _mxml_strcopy(buffer);
}
return (node);
@ -542,7 +542,7 @@ mxmlNewElement(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT`
// Create the node and set the element name...
if ((node = mxml_new(parent, MXML_TYPE_ELEMENT)) != NULL)
node->value.element.name = strdup(name);
node->value.element.name = _mxml_strcopy(name);
return (node);
}
@ -597,7 +597,7 @@ mxmlNewOpaque(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT`
// Create the node and set the element name...
if ((node = mxml_new(parent, MXML_TYPE_OPAQUE)) != NULL)
node->value.opaque = strdup(opaque);
node->value.opaque = _mxml_strcopy(opaque);
return (node);
}
@ -635,7 +635,7 @@ mxmlNewOpaquef(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT`
vsnprintf(buffer, sizeof(buffer), format, ap);
va_end(ap);
node->value.opaque = strdup(buffer);
node->value.opaque = _mxml_strcopy(buffer);
}
return (node);
@ -695,7 +695,7 @@ mxmlNewText(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT`
if ((node = mxml_new(parent, MXML_TYPE_TEXT)) != NULL)
{
node->value.text.whitespace = whitespace;
node->value.text.string = strdup(string);
node->value.text.string = _mxml_strcopy(string);
}
return (node);
@ -737,7 +737,7 @@ mxmlNewTextf(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT`
va_end(ap);
node->value.text.whitespace = whitespace;
node->value.text.string = strdup(buffer);
node->value.text.string = _mxml_strcopy(buffer);
}
return (node);
@ -854,26 +854,26 @@ mxml_free(mxml_node_t *node) // I - Node
switch (node->type)
{
case MXML_TYPE_CDATA :
free(node->value.cdata);
_mxml_strfree(node->value.cdata);
break;
case MXML_TYPE_COMMENT :
free(node->value.comment);
_mxml_strfree(node->value.comment);
break;
case MXML_TYPE_DECLARATION :
free(node->value.declaration);
_mxml_strfree(node->value.declaration);
break;
case MXML_TYPE_DIRECTIVE :
free(node->value.directive);
_mxml_strfree(node->value.directive);
break;
case MXML_TYPE_ELEMENT :
free(node->value.element.name);
_mxml_strfree(node->value.element.name);
if (node->value.element.num_attrs)
{
for (i = 0; i < node->value.element.num_attrs; i ++)
{
free(node->value.element.attrs[i].name);
free(node->value.element.attrs[i].value);
_mxml_strfree(node->value.element.attrs[i].name);
_mxml_strfree(node->value.element.attrs[i].value);
}
free(node->value.element.attrs);
@ -883,13 +883,13 @@ mxml_free(mxml_node_t *node) // I - Node
// Nothing to do
break;
case MXML_TYPE_OPAQUE :
free(node->value.opaque);
_mxml_strfree(node->value.opaque);
break;
case MXML_TYPE_REAL :
// Nothing to do
break;
case MXML_TYPE_TEXT :
free(node->value.text.string);
_mxml_strfree(node->value.text.string);
break;
case MXML_TYPE_CUSTOM :
if (node->value.custom.data && node->value.custom.destroy)

View File

@ -39,6 +39,184 @@
#endif // __sun
//
// 'mxmlSetCustomHandlers()' - Set the custom data callbacks.
//
// This function sets the callbacks that are used for loading and saving custom
// data types. The load callback `load_cb` accepts the callback data pointer
// `cbdata`, a node pointer, and a data string and returns `true` on success and
// `false` on error, for example:
//
// ```c
// typedef struct
// {
// unsigned year, /* Year */
// month, /* Month */
// day, /* Day */
// hour, /* Hour */
// minute, /* Minute */
// second; /* Second */
// time_t unix; /* UNIX time */
// } iso_date_time_t;
//
// bool
// my_custom_load_cb(void *cbdata, mxml_node_t *node, const char *data)
// {
// iso_date_time_t *dt;
// struct tm tmdata;
//
// /* Allocate custom data structure ... */
// dt = calloc(1, sizeof(iso_date_time_t));
//
// /* Parse the data string... */
// if (sscanf(data, "%u-%u-%uT%u:%u:%uZ", &(dt->year), &(dt->month),
// &(dt->day), &(dt->hour), &(dt->minute), &(dt->second)) != 6)
// {
// /* Unable to parse date and time numbers... */
// free(dt);
// return (false);
// }
//
// /* Range check values... */
// if (dt->month < 1 || dt->month > 12 || dt->day < 1 || dt->day > 31 ||
// dt->hour < 0 || dt->hour > 23 || dt->minute < 0 || dt->minute > 59 ||
// dt->second < 0 || dt->second > 60)
// {
// /* Date information is out of range... */
// free(dt);
// return (false);
// }
//
// /* Convert ISO time to UNIX time in seconds... */
// tmdata.tm_year = dt->year - 1900;
// tmdata.tm_mon = dt->month - 1;
// tmdata.tm_day = dt->day;
// tmdata.tm_hour = dt->hour;
// tmdata.tm_min = dt->minute;
// tmdata.tm_sec = dt->second;
//
// dt->unix = gmtime(&tmdata);
//
// /* Set custom data and free function... */
// mxmlSetCustom(node, data, free);
//
// /* Return with no errors... */
// return (true);
// }
// ```
//
// The save callback `save_cb` accepts the callback data pointer `cbdata` and a
// node pointer and returns a malloc'd string on success and `NULL` on error,
// for example:
//
// ```c
// char *
// my_custom_save_cb(void *cbdata, mxml_node_t *node)
// {
// char data[255];
// iso_date_time_t *dt;
//
// /* Get the custom data structure */
// dt = (iso_date_time_t *)mxmlGetCustom(node);
//
// /* Generate string version of the date/time... */
// snprintf(data, sizeof(data), "%04u-%02u-%02uT%02u:%02u:%02uZ",
// dt->year, dt->month, dt->day, dt->hour, dt->minute, dt->second);
//
// /* Duplicate the string and return... */
// return (strdup(data));
// }
// ```
//
void
mxmlSetCustomCallbacks(
mxml_custom_load_cb_t load_cb, // I - Load callback function
mxml_custom_save_cb_t save_cb, // I - Save callback function
void *cbdata) // I - Callback data
{
_mxml_global_t *global = _mxml_global();
// Global data
global->custom_load_cb = load_cb;
global->custom_save_cb = save_cb;
global->custom_cbdata = cbdata;
}
//
// 'mxmlSetErrorCallback()' - Set the error message callback.
//
// This function sets a function to use when reporting errors. The callback
// `cb` accepts the data pointer `cbdata` and a string pointer containing the
// error message:
//
// ```c
// void my_error_cb(void *cbdata, const char *message)
// {
// fprintf(stderr, "myprogram: %s\n", message);
// }
// ```
//
// The default error callback writes the error message to the `stderr` file.
//
void
mxmlSetErrorCallback(
mxml_error_cb_t cb, // I - Error callback function
void *cbdata) // I - Error callback data
{
_mxml_global_t *global = _mxml_global();
// Global data
global->error_cb = cb;
global->error_cbdata = cbdata;
}
//
// 'mxmlSetStringCallbacks()' - Set the string copy/free callback functions.
//
// This function sets the string copy/free callback functions for the current
// thread. The `strcopy_cb` function makes a copy of the provided string while
// the `strfree_cb` function frees the copy. Each callback accepts the
// `str_cbdata` pointer along with the pointer to the string:
//
// ```c
// char *my_strcopy_cb(void *cbdata, const char *s)
// {
// ... make a copy of "s" ...
// }
//
// void my_strfree_cb(void *cbdata, char *s)
// {
// ... release the memory used by "s" ...
// }
// ```
//
// The default `strcopy_cb` function calls `strdup` while the default
// `strfree_cb` function calls `free`.
//
void
mxmlSetStringCallbacks(
mxml_strcopy_cb_t strcopy_cb, // I - String copy callback function
mxml_strfree_cb_t strfree_cb, // I - String free callback function
void *str_cbdata) // I - String callback data
{
_mxml_global_t *global = _mxml_global();
// Global data
global->strcopy_cb = strcopy_cb;
global->strfree_cb = strfree_cb;
global->str_cbdata = str_cbdata;
}
//
// '_mxml_error()' - Display an error message.
//
@ -70,6 +248,48 @@ _mxml_error(const char *format, // I - Printf-style format string
}
//
// '_mxml_strcopy()' - Copy a string.
//
char * // O - Copy of string
_mxml_strcopy(const char *s) // I - String
{
_mxml_global_t *global = _mxml_global();
// Global data
if (!s)
return (NULL);
if (global->strcopy_cb)
return ((global->strcopy_cb)(global->str_cbdata, s));
else
return (strdup(s));
}
//
// '_mxml_strfree()' - Free a string.
//
void
_mxml_strfree(char *s) // I - String
{
_mxml_global_t *global = _mxml_global();
// Global data
if (!s)
return;
if (global->strfree_cb)
(global->strfree_cb)(global->str_cbdata, s);
else
free((void *)s);
}
#ifdef HAVE_PTHREAD_H // POSIX threading
# include <pthread.h>

View File

@ -107,6 +107,9 @@ typedef struct _mxml_global_s // Global, per-thread data
struct lconv *loc; // Locale data
size_t loc_declen; // Length of decimal point string
bool loc_set; // Locale data set?
mxml_strcopy_cb_t strcopy_cb; // String copy callback function
mxml_strfree_cb_t strfree_cb; // String free callback function
void *str_cbdata; // String callback data
int wrap; // Wrap margin
} _mxml_global_t;
@ -119,6 +122,7 @@ extern _mxml_global_t *_mxml_global(void);
extern int _mxml_entity_cb(void *cbdata, const char *name);
extern const char *_mxml_entity_string(int ch);
extern void _mxml_error(const char *format, ...) MXML_FORMAT(1,2);
extern char *_mxml_strcopy(const char *s);
extern void _mxml_strfree(char *s);
#endif // !MXML_PRIVATE_H

View File

@ -47,13 +47,13 @@ mxmlSetCDATA(mxml_node_t *node, // I - Node to set
}
// Allocate the new value, free any old element value, and set the new value...
if ((s = strdup(data)) == NULL)
if ((s = _mxml_strcopy(data)) == NULL)
{
_mxml_error("Unable to allocate memory for CDATA.");
return (false);
}
free(node->value.cdata);
_mxml_strfree(node->value.cdata);
node->value.cdata = s;
return (true);
@ -94,13 +94,13 @@ mxmlSetCDATAf(mxml_node_t *node, // I - Node
vsnprintf(buffer, sizeof(buffer), format, ap);
va_end(ap);
if ((s = strdup(buffer)) == NULL)
if ((s = _mxml_strcopy(buffer)) == NULL)
{
_mxml_error("Unable to allocate memory for CDATA string.");
return (false);
}
free(node->value.cdata);
_mxml_strfree(node->value.cdata);
node->value.cdata = s;
return (true);
@ -137,13 +137,13 @@ mxmlSetComment(mxml_node_t *node, // I - Node
return (true);
// Free any old string value and set the new value...
if ((s = strdup(comment)) == NULL)
if ((s = _mxml_strcopy(comment)) == NULL)
{
_mxml_error("Unable to allocate memory for comment string.");
return (false);
}
free(node->value.comment);
_mxml_strfree(node->value.comment);
node->value.comment = s;
return (true);
@ -184,13 +184,13 @@ mxmlSetCommentf(mxml_node_t *node, // I - Node
vsnprintf(buffer, sizeof(buffer), format, ap);
va_end(ap);
if ((s = strdup(buffer)) == NULL)
if ((s = _mxml_strcopy(buffer)) == NULL)
{
_mxml_error("Unable to allocate memory for comment string.");
return (false);
}
free(node->value.comment);
_mxml_strfree(node->value.comment);
node->value.comment = s;
return (true);
@ -267,13 +267,13 @@ mxmlSetDeclaration(
return (true);
// Free any old string value and set the new value...
if ((s = strdup(declaration)) == NULL)
if ((s = _mxml_strcopy(declaration)) == NULL)
{
_mxml_error("Unable to allocate memory for declaration string.");
return (false);
}
free(node->value.declaration);
_mxml_strfree(node->value.declaration);
node->value.declaration = s;
return (true);
@ -314,13 +314,13 @@ mxmlSetDeclarationf(mxml_node_t *node, // I - Node
vsnprintf(buffer, sizeof(buffer), format, ap);
va_end(ap);
if ((s = strdup(buffer)) == NULL)
if ((s = _mxml_strcopy(buffer)) == NULL)
{
_mxml_error("Unable to allocate memory for declaration string.");
return (false);
}
free(node->value.declaration);
_mxml_strfree(node->value.declaration);
node->value.declaration = s;
return (true);
@ -357,13 +357,13 @@ mxmlSetDirective(mxml_node_t *node, // I - Node
return (true);
// Free any old string value and set the new value...
if ((s = strdup(directive)) == NULL)
if ((s = _mxml_strcopy(directive)) == NULL)
{
_mxml_error("Unable to allocate memory for directive string.");
return (false);
}
free(node->value.directive);
_mxml_strfree(node->value.directive);
node->value.directive = s;
return (true);
@ -404,13 +404,13 @@ mxmlSetDirectivef(mxml_node_t *node, // I - Node
vsnprintf(buffer, sizeof(buffer), format, ap);
va_end(ap);
if ((s = strdup(buffer)) == NULL)
if ((s = _mxml_strcopy(buffer)) == NULL)
{
_mxml_error("Unable to allocate memory for directive string.");
return (false);
}
free(node->value.directive);
_mxml_strfree(node->value.directive);
node->value.directive = s;
return (true);
@ -446,13 +446,13 @@ mxmlSetElement(mxml_node_t *node, // I - Node to set
return (true);
// Free any old element value and set the new value...
if ((s = strdup(name)) == NULL)
if ((s = _mxml_strcopy(name)) == NULL)
{
_mxml_error("Unable to allocate memory for element name.");
return (false);
}
free(node->value.element.name);
_mxml_strfree(node->value.element.name);
node->value.element.name = s;
return (true);
@ -518,13 +518,13 @@ mxmlSetOpaque(mxml_node_t *node, // I - Node to set
return (true);
// Free any old opaque value and set the new value...
if ((s = strdup(opaque)) == NULL)
if ((s = _mxml_strcopy(opaque)) == NULL)
{
_mxml_error("Unable to allocate memory for opaque string.");
return (false);
}
free(node->value.opaque);
_mxml_strfree(node->value.opaque);
node->value.opaque = s;
return (true);
@ -567,13 +567,13 @@ mxmlSetOpaquef(mxml_node_t *node, // I - Node to set
vsnprintf(buffer, sizeof(buffer), format, ap);
va_end(ap);
if ((s = strdup(buffer)) == NULL)
if ((s = _mxml_strcopy(buffer)) == NULL)
{
_mxml_error("Unable to allocate memory for opaque string.");
return (false);
}
free(node->value.opaque);
_mxml_strfree(node->value.opaque);
node->value.opaque = s;
return (true);
@ -646,13 +646,13 @@ mxmlSetText(mxml_node_t *node, // I - Node to set
}
// Free any old string value and set the new value...
if ((s = strdup(string)) == NULL)
if ((s = _mxml_strcopy(string)) == NULL)
{
_mxml_error("Unable to allocate memory for text string.");
return (false);
}
free(node->value.text.string);
_mxml_strfree(node->value.text.string);
node->value.text.whitespace = whitespace;
node->value.text.string = s;
@ -698,13 +698,13 @@ mxmlSetTextf(mxml_node_t *node, // I - Node to set
vsnprintf(buffer, sizeof(buffer), format, ap);
va_end(ap);
if ((s = strdup(buffer)) == NULL)
if ((s = _mxml_strcopy(buffer)) == NULL)
{
_mxml_error("Unable to allocate memory for text string.");
return (false);
}
free(node->value.text.string);
_mxml_strfree(node->value.text.string);
node->value.text.whitespace = whitespace;
node->value.text.string = s;

8
mxml.h
View File

@ -124,6 +124,11 @@ typedef ssize_t (*mxml_read_cb_t)(void *cbdata, void *buffer, size_t bytes);
typedef const char *(*mxml_save_cb_t)(void *cbdata, mxml_node_t *node, mxml_ws_t when);
// Save callback function
typedef char *(*mxml_strcopy_cb_t)(void *cbdata, const char *s);
// String copy/allocation callback
typedef void (*mxml_strfree_cb_t)(void *cbdata, char *s);
// String free callback
typedef ssize_t (*mxml_write_cb_t)(void *cbdata, const void *buffer, size_t bytes);
// Write callback function
@ -220,7 +225,7 @@ extern bool mxmlSetDeclaration(mxml_node_t *node, const char *declaration);
extern bool mxmlSetDeclarationf(mxml_node_t *node, const char *format, ...) MXML_FORMAT(2,3);
extern bool mxmlSetDirective(mxml_node_t *node, const char *directive);
extern bool mxmlSetDirectivef(mxml_node_t *node, const char *format, ...) MXML_FORMAT(2,3);
extern bool mxmlSetCustom(mxml_node_t *node, void *data, mxml_custom_destroy_cb_t destroy);
extern bool mxmlSetCustom(mxml_node_t *node, void *data, mxml_custom_destroy_cb_t destroy_cb);
extern void mxmlSetCustomCallbacks(mxml_custom_load_cb_t load_cb, mxml_custom_save_cb_t save_cb, void *cbdata);
extern bool mxmlSetElement(mxml_node_t *node, const char *name);
extern void mxmlSetErrorCallback(mxml_error_cb_t cb, void *cbdata);
@ -228,6 +233,7 @@ extern bool mxmlSetInteger(mxml_node_t *node, long integer);
extern bool mxmlSetOpaque(mxml_node_t *node, const char *opaque);
extern bool mxmlSetOpaquef(mxml_node_t *node, const char *format, ...) MXML_FORMAT(2,3);
extern bool mxmlSetReal(mxml_node_t *node, double real);
extern void mxmlSetStringCallbacks(mxml_strcopy_cb_t strcopy_cb, mxml_strfree_cb_t strfree_cb, void *str_cbdata);
extern bool mxmlSetText(mxml_node_t *node, bool whitespace, const char *string);
extern bool mxmlSetTextf(mxml_node_t *node, bool whitespace, const char *format, ...) MXML_FORMAT(3,4);
extern bool mxmlSetUserData(mxml_node_t *node, void *data);