From 45d7a29cbaa2022e303a48a2661fb8d3423f0006 Mon Sep 17 00:00:00 2001 From: icex2 Date: Thu, 15 Aug 2024 11:34:31 +0200 Subject: [PATCH] feat: Add mxml I checked out the few xml implementations that are available as pure C and this one seemed like the best choice so far. It's fairly small regarding foot print, similar API to the property (node) API, and I could get it to work fairly easily with a few sandbox examples thanks to decent documentation. This serves as the base for a bemanitools property (node) implementation for games that do not come with AVS 2 and the property API. --- Module.mk | 2 + src/main/mxml/Module.mk | 14 + src/main/mxml/config.h | 49 + src/main/mxml/mxml-attr.c | 271 ++++ src/main/mxml/mxml-file.c | 2275 ++++++++++++++++++++++++++++++++++ src/main/mxml/mxml-get.c | 364 ++++++ src/main/mxml/mxml-index.c | 418 +++++++ src/main/mxml/mxml-node.c | 937 ++++++++++++++ src/main/mxml/mxml-options.c | 545 ++++++++ src/main/mxml/mxml-private.c | 280 +++++ src/main/mxml/mxml-private.h | 140 +++ src/main/mxml/mxml-search.c | 237 ++++ src/main/mxml/mxml-set.c | 630 ++++++++++ src/main/mxml/mxml.h | 250 ++++ 14 files changed, 6412 insertions(+) create mode 100644 src/main/mxml/Module.mk create mode 100644 src/main/mxml/config.h create mode 100644 src/main/mxml/mxml-attr.c create mode 100644 src/main/mxml/mxml-file.c create mode 100644 src/main/mxml/mxml-get.c create mode 100644 src/main/mxml/mxml-index.c create mode 100644 src/main/mxml/mxml-node.c create mode 100644 src/main/mxml/mxml-options.c create mode 100644 src/main/mxml/mxml-private.c create mode 100644 src/main/mxml/mxml-private.h create mode 100644 src/main/mxml/mxml-search.c create mode 100644 src/main/mxml/mxml-set.c create mode 100644 src/main/mxml/mxml.h diff --git a/Module.mk b/Module.mk index 9fc8e59..16142c3 100644 --- a/Module.mk +++ b/Module.mk @@ -85,6 +85,8 @@ avsvers_64 := 1700 1603 1601 1509 1508 imps += avs avs-ea3 +include src/main/mxml/Module.mk + include src/main/aciodrv/Module.mk include src/main/aciodrv-proc/Module.mk include src/main/acioemu/Module.mk diff --git a/src/main/mxml/Module.mk b/src/main/mxml/Module.mk new file mode 100644 index 0000000..303cb21 --- /dev/null +++ b/src/main/mxml/Module.mk @@ -0,0 +1,14 @@ +libs += mxml + +libs_mxml := \ + +src_mxml := \ + mxml-get.c \ + mxml-options.c \ + mxml-search.c \ + mxml-attr.c \ + mxml-index.c \ + mxml-private.c \ + mxml-set.c \ + mxml-file.c \ + mxml-node.c \ diff --git a/src/main/mxml/config.h b/src/main/mxml/config.h new file mode 100644 index 0000000..dcb50a0 --- /dev/null +++ b/src/main/mxml/config.h @@ -0,0 +1,49 @@ +/* config.h. Generated from config.h.in by configure. */ +// +// Configuration file for Mini-XML, a small XML file parsing library. +// +// https://www.msweet.org/mxml +// +// Copyright © 2003-2024 by Michael R Sweet. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#ifndef MXML_CONFIG_H +# define MXML_CONFIG_H +# include +# include +# include +# include +# include + +// +// Version number +// + +# define MXML_VERSION "Mini-XML v4.0.3" + + +// +// Inline function support +// + +# define inline + + +// +// Long long support +// + +# define HAVE_LONG_LONG_INT 1 + + +// +// Have ? +// + +// # define HAVE_PTHREAD_H 1 + + +#endif // !MXML_CONFIG_H diff --git a/src/main/mxml/mxml-attr.c b/src/main/mxml/mxml-attr.c new file mode 100644 index 0000000..98dd5f5 --- /dev/null +++ b/src/main/mxml/mxml-attr.c @@ -0,0 +1,271 @@ +// +// Attribute support code for Mini-XML, a small XML file parsing library. +// +// https://www.msweet.org/mxml +// +// Copyright © 2003-2024 by Michael R Sweet. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "mxml-private.h" + + +// +// Local functions... +// + +static bool mxml_set_attr(mxml_node_t *node, const char *name, char *value); + + +// +// 'mxmlElementClearAttr()' - Remove an attribute from an element. +// +// This function removes the attribute `name` from the element `node`. +// + +void +mxmlElementClearAttr(mxml_node_t *node, // I - Element + const char *name) // I - Attribute name +{ + size_t i; // Looping var + _mxml_attr_t *attr; // Cirrent attribute + + + MXML_DEBUG("mxmlElementClearAttr(node=%p, name=\"%s\")\n", node, name ? name : "(null)"); + + // Range check input... + if (!node || node->type != MXML_TYPE_ELEMENT || !name) + return; + + // Look for the attribute... + for (i = node->value.element.num_attrs, attr = node->value.element.attrs; i > 0; i --, attr ++) + { + MXML_DEBUG("mxmlElementClearAttr: %s=\"%s\"\n", attr->name, attr->value); + + if (!strcmp(attr->name, name)) + { + // Delete this attribute... + _mxml_strfree(attr->name); + _mxml_strfree(attr->value); + + i --; + if (i > 0) + memmove(attr, attr + 1, i * sizeof(_mxml_attr_t)); + + node->value.element.num_attrs --; + + if (node->value.element.num_attrs == 0) + free(node->value.element.attrs); + return; + } + } +} + + +// +// 'mxmlElementGetAttr()' - Get the value of an attribute. +// +// This function gets the value for the attribute `name` from the element +// `node`. `NULL` is returned if the node is not an element or the named +// attribute does not exist. +// + +const char * // O - Attribute value or `NULL` +mxmlElementGetAttr(mxml_node_t *node, // I - Element node + const char *name) // I - Name of attribute +{ + size_t i; // Looping var + _mxml_attr_t *attr; // Cirrent attribute + + + MXML_DEBUG("mxmlElementGetAttr(node=%p, name=\"%s\")\n", node, name ? name : "(null)"); + + // Range check input... + if (!node || node->type != MXML_TYPE_ELEMENT || !name) + return (NULL); + + // Look for the attribute... + for (i = node->value.element.num_attrs, attr = node->value.element.attrs; i > 0; i --, attr ++) + { + MXML_DEBUG("mxmlElementGetAttr: %s=\"%s\"\n", attr->name, attr->value); + + if (!strcmp(attr->name, name)) + { + MXML_DEBUG("mxmlElementGetAttr: Returning \"%s\".\n", attr->value); + return (attr->value); + } + } + + // Didn't find attribute, so return NULL... + MXML_DEBUG("mxmlElementGetAttr: Returning NULL.\n"); + + return (NULL); +} + + +// +// 'mxmlElementGetAttrByIndex()' - Get an attribute by index. +// +// This function returned the Nth (`idx`) attribute for element `node`. The +// attribute name is optionallly returned in the `name` argument. `NULL` is +// returned if node is not an element or the specified index is out of range. +// + +const char * // O - Attribute value +mxmlElementGetAttrByIndex( + mxml_node_t *node, // I - Node + size_t idx, // I - Attribute index, starting at `0` + const char **name) // O - Attribute name or `NULL` to not return it +{ + if (!node || node->type != MXML_TYPE_ELEMENT || idx >= node->value.element.num_attrs) + return (NULL); + + if (name) + *name = node->value.element.attrs[idx].name; + + return (node->value.element.attrs[idx].value); +} + + +// +// 'mxmlElementGetAttrCount()' - Get the number of element attributes. +// +// This function returns the number of attributes for the element `node`. `0` +// is returned if the node is not an element or there are no attributes for the +// element. +// + +size_t // O - Number of attributes +mxmlElementGetAttrCount( + mxml_node_t *node) // I - Node +{ + if (node && node->type == MXML_TYPE_ELEMENT) + return (node->value.element.num_attrs); + else + return (0); +} + + +// +// 'mxmlElementSetAttr()' - Set an attribute for an element. +// +// This function sets attribute `name` to the string `value` for the element +// `node`. If the named attribute already exists, the value of the attribute +// is replaced by the new string value. The string value is copied. +// + +void +mxmlElementSetAttr(mxml_node_t *node, // I - Element node + const char *name, // I - Name of attribute + const char *value) // I - Attribute value +{ + char *valuec; // Copy of value + + + MXML_DEBUG("mxmlElementSetAttr(node=%p, name=\"%s\", value=\"%s\")\n", node, name ? name : "(null)", value ? value : "(null)"); + + // Range check input... + if (!node || node->type != MXML_TYPE_ELEMENT || !name) + return; + + if (value) + { + if ((valuec = _mxml_strcopy(value)) == NULL) + return; + } + else + { + valuec = NULL; + } + + if (!mxml_set_attr(node, name, valuec)) + _mxml_strfree(valuec); +} + + +// +// 'mxmlElementSetAttrf()' - Set an attribute with a formatted value. +// +// This function sets attribute `name` to the formatted value of `format` for +// the element `node`. If the named attribute already exists, the value of the +// attribute is replaced by the new formatted string value. +// + +void +mxmlElementSetAttrf(mxml_node_t *node, // I - Element node + const char *name, // I - Name of attribute + const char *format,// I - Printf-style attribute value + ...) // I - Additional arguments as needed +{ + va_list ap; // Argument pointer + char buffer[16384]; // Format buffer + char *value; // Value + + + MXML_DEBUG("mxmlElementSetAttrf(node=%p, name=\"%s\", format=\"%s\", ...)\n", node, name ? name : "(null)", format ? format : "(null)"); + + // Range check input... + if (!node || node->type != MXML_TYPE_ELEMENT || !name || !format) + return; + + // Format the value... + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + if ((value = _mxml_strcopy(buffer)) != NULL) + { + if (!mxml_set_attr(node, name, value)) + _mxml_strfree(value); + } +} + + +// +// 'mxml_set_attr()' - Set or add an attribute name/value pair. +// + +static bool // O - `true` on success, `false` on failure +mxml_set_attr(mxml_node_t *node, // I - Element node + const char *name, // I - Attribute name + char *value) // I - Attribute value +{ + int i; // Looping var + _mxml_attr_t *attr; // New attribute + + + // Look for the attribute... + for (i = node->value.element.num_attrs, attr = node->value.element.attrs; i > 0; i --, attr ++) + { + if (!strcmp(attr->name, name)) + { + // Free the old value as needed... + _mxml_strfree(attr->value); + attr->value = value; + + return (true); + } + } + + // Add a new attribute... + if ((node->value.element.num_attrs % MXML_ALLOC_SIZE) == 0) + { + if ((attr = realloc(node->value.element.attrs, (node->value.element.num_attrs + MXML_ALLOC_SIZE) * sizeof(_mxml_attr_t))) == NULL) + return (false); + + node->value.element.attrs = attr; + } + + attr = node->value.element.attrs + node->value.element.num_attrs; + + if ((attr->name = _mxml_strcopy(name)) == NULL) + return (false); + + attr->value = value; + + node->value.element.num_attrs ++; + + return (true); +} diff --git a/src/main/mxml/mxml-file.c b/src/main/mxml/mxml-file.c new file mode 100644 index 0000000..d030e3f --- /dev/null +++ b/src/main/mxml/mxml-file.c @@ -0,0 +1,2275 @@ +// +// File loading code for Mini-XML, a small XML file parsing library. +// +// https://www.msweet.org/mxml +// +// Copyright © 2003-2024 by Michael R Sweet. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#ifndef _WIN32 +# include +#endif // !_WIN32 +#include "mxml-private.h" + + +// +// Local types... +// + +typedef enum _mxml_encoding_e // Character encoding +{ + _MXML_ENCODING_UTF8, // UTF-8 + _MXML_ENCODING_UTF16BE, // UTF-16 Big-Endian + _MXML_ENCODING_UTF16LE // UTF-16 Little-Endian +} _mxml_encoding_t; + +typedef struct _mxml_stringbuf_s // String buffer +{ + char *buffer, // Buffer + *bufptr; // Pointer into buffer + size_t bufsize; // Size of buffer + bool bufalloc; // Allocate buffer? +} _mxml_stringbuf_t; + + +// +// Macro to test for a bad XML character... +// + +#define mxml_bad_char(ch) ((ch) < ' ' && (ch) != '\n' && (ch) != '\r' && (ch) != '\t') + + +// +// Local functions... +// + +static bool mxml_add_char(mxml_options_t *options, int ch, char **ptr, char **buffer, size_t *bufsize); +static int mxml_get_entity(mxml_options_t *options, mxml_io_cb_t io_cb, void *io_cbdata, _mxml_encoding_t *encoding, mxml_node_t *parent, int *line); +static int mxml_getc(mxml_options_t *options, mxml_io_cb_t io_cb, void *io_cbdata, _mxml_encoding_t *encoding); +static inline int mxml_isspace(int ch) + { + return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'); + } +static mxml_node_t *mxml_load_data(mxml_node_t *top, mxml_options_t *options, mxml_io_cb_t io_cb, void *io_cbdata); +static int mxml_parse_element(mxml_options_t *options, mxml_io_cb_t io_cb, void *io_cbdata, mxml_node_t *node, _mxml_encoding_t *encoding, int *line); +// static size_t mxml_read_cb_fd(int *fd, void *buffer, size_t bytes); +static size_t mxml_read_cb_file(FILE *fp, void *buffer, size_t bytes); +static size_t mxml_read_cb_string(_mxml_stringbuf_t *sb, void *buffer, size_t bytes); +static double mxml_strtod(mxml_options_t *options, const char *buffer, char **bufptr); +// static size_t mxml_io_cb_fd(int *fd, void *buffer, size_t bytes); +static size_t mxml_io_cb_file(FILE *fp, void *buffer, size_t bytes); +static size_t mxml_io_cb_string(_mxml_stringbuf_t *sb, void *buffer, size_t bytes); +static int mxml_write_node(mxml_node_t *node, mxml_options_t *options, mxml_io_cb_t io_cb, void *io_cbdata, int col); +static int mxml_write_string(const char *s, mxml_io_cb_t io_cb, void *io_cbdata, bool use_entities, int col); +static int mxml_write_ws(mxml_node_t *node, mxml_options_t *options, mxml_io_cb_t io_cb, void *io_cbdata, mxml_ws_t ws, int col); + + +// +// 'mxmlLoadFd()' - Load a file descriptor into an XML node tree. +// +// This function loads the file descriptor `fd` into an XML node tree. The +// nodes in the specified file are added to the specified node `top` - if `NULL` +// the XML file MUST be well-formed with a single parent processing instruction +// node like `` at the start of the file. +// +// Load options are provides via the `options` argument. If `NULL`, all values +// will be loaded into `MXML_TYPE_TEXT` nodes. Use the @link mxmlOptionsNew@ +// function to create options when loading XML data. +// + +// mxml_node_t * // O - First node or `NULL` if the file could not be read. +// mxmlLoadFd( +// mxml_node_t *top, // I - Top node +// mxml_options_t *options, // I - Options +// int fd) // I - File descriptor to read from +// { +// // Range check input... +// if (fd < 0) +// return (NULL); + +// // Read the XML data... +// return (mxml_load_data(top, options, (mxml_io_cb_t)mxml_read_cb_fd, &fd)); +// } + + +// +// 'mxmlLoadFile()' - Load a file into an XML node tree. +// +// This function loads the `FILE` pointer `fp` into an XML node tree. The +// nodes in the specified file are added to the specified node `top` - if `NULL` +// the XML file MUST be well-formed with a single parent processing instruction +// node like `` at the start of the file. +// +// Load options are provides via the `options` argument. If `NULL`, all values +// will be loaded into `MXML_TYPE_TEXT` nodes. Use the @link mxmlOptionsNew@ +// function to create options when loading XML data. +// + +mxml_node_t * // O - First node or `NULL` if the file could not be read. +mxmlLoadFile( + mxml_node_t *top, // I - Top node + mxml_options_t *options, // I - Options + FILE *fp) // I - File to read from +{ + // Range check input... + if (!fp) + return (NULL); + + // Read the XML data... + return (mxml_load_data(top, options, (mxml_io_cb_t)mxml_read_cb_file, fp)); +} + + +// +// 'mxmlLoadFilename()' - Load a file into an XML node tree. +// +// This function loads the named file `filename` into an XML node tree. The +// nodes in the specified file are added to the specified node `top` - if `NULL` +// the XML file MUST be well-formed with a single parent processing instruction +// node like `` at the start of the file. +// +// Load options are provides via the `options` argument. If `NULL`, all values +// will be loaded into `MXML_TYPE_TEXT` nodes. Use the @link mxmlOptionsNew@ +// function to create options when loading XML data. +// + +mxml_node_t * // O - First node or `NULL` if the file could not be read. +mxmlLoadFilename( + mxml_node_t *top, // I - Top node + mxml_options_t *options, // I - Options + const char *filename) // I - File to read from +{ + FILE *fp; // File pointer + mxml_node_t *ret; // Node + + + // Range check input... + if (!filename) + return (NULL); + + // Open the file... + if ((fp = fopen(filename, "r")) == NULL) + return (NULL); + + // Read the XML data... + ret = mxml_load_data(top, options, (mxml_io_cb_t)mxml_read_cb_file, fp); + + // Close the file and return... + fclose(fp); + + return (ret); +} + + +// +// 'mxmlLoadIO()' - Load an XML node tree using a read callback. +// +// This function loads data into an XML node tree using a read callback. The +// nodes in the specified file are added to the specified node `top` - if `NULL` +// the XML file MUST be well-formed with a single parent processing instruction +// node like `` at the start of the file. +// +// Load options are provides via the `options` argument. If `NULL`, all values +// will be loaded into `MXML_TYPE_TEXT` nodes. Use the @link mxmlOptionsNew@ +// function to create options when loading XML data. +// +// The read callback function `io_cb` is called to read a number of bytes from +// the source. The callback data pointer `io_cbdata` is passed to the read +// callback with a pointer to a buffer and the maximum number of bytes to read, +// for example: +// +// ```c +// size_t my_io_cb(void *cbdata, void *buffer, size_t bytes) +// { +// ... copy up to "bytes" bytes into buffer ... +// ... return the number of bytes "read" or 0 on error ... +// } +// ``` +// + +mxml_node_t * // O - First node or `NULL` if the file could not be read. +mxmlLoadIO( + mxml_node_t *top, // I - Top node + mxml_options_t *options, // I - Options + mxml_io_cb_t io_cb, // I - Read callback function + void *io_cbdata) // I - Read callback data +{ + // Range check input... + if (!io_cb) + return (NULL); + + // Read the XML data... + return (mxml_load_data(top, options, io_cb, io_cbdata)); +} + + +// +// 'mxmlLoadString()' - Load a string into an XML node tree. +// +// This function loads the string into an XML node tree. The nodes in the +// specified file are added to the specified node `top` - if `NULL` the XML file +// MUST be well-formed with a single parent processing instruction node like +// `` at the start of the file. +// +// Load options are provides via the `options` argument. If `NULL`, all values +// will be loaded into `MXML_TYPE_TEXT` nodes. Use the @link mxmlOptionsNew@ +// function to create options when loading XML data. +// + +mxml_node_t * // O - First node or `NULL` if the string has errors. +mxmlLoadString( + mxml_node_t *top, // I - Top node + mxml_options_t *options, // I - Options + const char *s) // I - String to load +{ + _mxml_stringbuf_t sb; // String buffer + + + // Range check input... + if (!s) + return (NULL); + + // Setup string buffer... + sb.buffer = (char *)s; + sb.bufptr = (char *)s; + sb.bufsize = strlen(s); + sb.bufalloc = false; + + // Read the XML data... + return (mxml_load_data(top, options, (mxml_io_cb_t)mxml_read_cb_string, &sb)); +} + + +// +// 'mxmlSaveAllocString()' - Save an XML tree to an allocated string. +// +// This function saves the XML tree `node` to an allocated string. The string +// should be freed using `free` (or the string free callback set using +// @link mxmlSetStringCallbacks@) when you are done with it. +// +// `NULL` is returned if the node would produce an empty string or if the string +// cannot be allocated. +// +// Save options are provides via the `options` argument. If `NULL`, the XML +// output will be wrapped at column 72 with no additional whitespace. Use the +// @link mxmlOptionsNew@ function to create options for saving XML data. +// + +char * // O - Allocated string or `NULL` +mxmlSaveAllocString( + mxml_node_t *node, // I - Node to write + mxml_options_t *options) // I - Options +{ + _mxml_stringbuf_t sb; // String buffer + + + // Setup a string buffer + if ((sb.buffer = malloc(1024)) == NULL) + return (NULL); + + sb.bufptr = sb.buffer; + sb.bufsize = 1024; + sb.bufalloc = true; + + // Write the top node... + if (mxml_write_node(node, options, (mxml_io_cb_t)mxml_io_cb_string, &sb, 0) < 0) + { + free(sb.buffer); + return (NULL); + } + + // Nul-terminate the string... + *(sb.bufptr) = '\0'; + + // Return the allocated string... + return (sb.buffer); +} + + +// +// 'mxmlSaveFd()' - Save an XML tree to a file descriptor. +// +// This function saves the XML tree `node` to a file descriptor. +// +// Save options are provides via the `options` argument. If `NULL`, the XML +// output will be wrapped at column 72 with no additional whitespace. Use the +// @link mxmlOptionsNew@ function to create options for saving XML data. +// + +// bool // O - `true` on success, `false` on error. +// mxmlSaveFd(mxml_node_t *node, // I - Node to write +// mxml_options_t *options, // I - Options +// int fd) // I - File descriptor to write to +// { +// int col; // Final column + + +// // Write the node... +// if ((col = mxml_write_node(node, options, (mxml_io_cb_t)mxml_io_cb_fd, &fd, 0)) < 0) +// return (false); + +// // Make sure the file ends with a newline... +// if (col > 0) +// { +// if (write(fd, "\n", 1) < 0) +// return (false); +// } + +// return (true); +// } + + +// +// 'mxmlSaveFile()' - Save an XML tree to a file. +// +// This function saves the XML tree `node` to a stdio `FILE`. +// +// Save options are provides via the `options` argument. If `NULL`, the XML +// output will be wrapped at column 72 with no additional whitespace. Use the +// @link mxmlOptionsNew@ function to create options for saving XML data. +// + +bool // O - `true` on success, `false` on error. +mxmlSaveFile( + mxml_node_t *node, // I - Node to write + mxml_options_t *options, // I - Options + FILE *fp) // I - File to write to +{ + int col; // Final column + + + // Write the node... + if ((col = mxml_write_node(node, options, (mxml_io_cb_t)mxml_io_cb_file, fp, 0)) < 0) + return (false); + + // Make sure the file ends with a newline... + if (col > 0) + { + if (putc('\n', fp) < 0) + return (false); + } + + return (true); +} + + +// +// 'mxmlSaveFilename()' - Save an XML tree to a file. +// +// This function saves the XML tree `node` to a named file. +// +// Save options are provides via the `options` argument. If `NULL`, the XML +// output will be wrapped at column 72 with no additional whitespace. Use the +// @link mxmlOptionsNew@ function to create options for saving XML data. +// + +bool // O - `true` on success, `false` on error. +mxmlSaveFilename( + mxml_node_t *node, // I - Node to write + mxml_options_t *options, // I - Options + const char *filename) // I - File to write to +{ + bool ret = true; // Return value + FILE *fp; // File pointer + int col; // Final column + + + // Open the file... + if ((fp = fopen(filename, "w")) == NULL) + return (false); + + // Write the node... + if ((col = mxml_write_node(node, options, (mxml_io_cb_t)mxml_io_cb_file, fp, 0)) < 0) + { + ret = false; + } + else if (col > 0) + { + // Make sure the file ends with a newline... + if (putc('\n', fp) < 0) + ret = false; + } + + fclose(fp); + + return (ret); +} + + +// +// 'mxmlSaveIO()' - Save an XML tree using a callback. +// +// This function saves the XML tree `node` using a write callback function +// `io_cb`. The write callback is called with the callback data pointer +// `io_cbdata`, a buffer pointer, and the number of bytes to write, for +// example: +// +// ```c +// size_t my_io_cb(void *cbdata, const void *buffer, size_t bytes) +// { +// ... write/copy bytes from buffer to the output ... +// ... return the number of bytes written/copied or 0 on error ... +// } +// ``` +// +// Save options are provides via the `options` argument. If `NULL`, the XML +// output will be wrapped at column 72 with no additional whitespace. Use the +// @link mxmlOptionsNew@ function to create options for saving XML data. +// + +bool // O - `true` on success, `false` on error. +mxmlSaveIO( + mxml_node_t *node, // I - Node to write + mxml_options_t *options, // I - Options + mxml_io_cb_t io_cb, // I - Write callback function + void *io_cbdata) // I - Write callback data +{ + int col; // Final column + + + // Range check input... + if (!node || !io_cb) + return (false); + + // Write the node... + if ((col = mxml_write_node(node, options, io_cb, io_cbdata, 0)) < 0) + return (false); + + if (col > 0) + { + // Make sure the file ends with a newline... + if ((io_cb)(io_cbdata, "\n", 1) != 1) + return (false); + } + + return (true); +} + + +// +// 'mxmlSaveString()' - Save an XML node tree to a string. +// +// This function saves the XML tree `node` to a fixed-size string buffer. +// +// Save options are provides via the `options` argument. If `NULL`, the XML +// output will be wrapped at column 72 with no additional whitespace. Use the +// @link mxmlOptionsNew@ function to create options for saving XML data. +// + +size_t // O - Size of string +mxmlSaveString( + mxml_node_t *node, // I - Node to write + mxml_options_t *options, // I - Options + char *buffer, // I - String buffer + size_t bufsize) // I - Size of string buffer +{ + _mxml_stringbuf_t sb; // String buffer + + + // Setup the string buffer... + sb.buffer = buffer; + sb.bufptr = buffer; + sb.bufsize = bufsize; + sb.bufalloc = false; + + // Write the node... + if (mxml_write_node(node, options, (mxml_io_cb_t)mxml_io_cb_string, &sb, 0) < 0) + return (false); + + // Nul-terminate the string... + if (sb.bufptr < (sb.buffer + sb.bufsize)) + *(sb.bufptr) = '\0'; + + // Return the number of characters... + return ((size_t)(sb.bufptr - sb.buffer)); +} + + +// +// 'mxml_add_char()' - Add a character to a buffer, expanding as needed. +// + +static bool // O - `true` on success, `false` on error +mxml_add_char(mxml_options_t *options, // I - Options + int ch, // I - Character to add + char **bufptr, // IO - Current position in buffer + char **buffer, // IO - Current buffer + size_t *bufsize) // IO - Current buffer size +{ + char *newbuffer; // New buffer value + + + if (*bufptr >= (*buffer + *bufsize - 4)) + { + // Increase the size of the buffer... + if (*bufsize < 1024) + (*bufsize) *= 2; + else + (*bufsize) += 1024; + + if ((newbuffer = realloc(*buffer, *bufsize)) == NULL) + { + _mxml_error(options, "Unable to expand string buffer to %lu bytes.", (unsigned long)*bufsize); + + return (false); + } + + *bufptr = newbuffer + (*bufptr - *buffer); + *buffer = newbuffer; + } + + if (ch < 0x80) + { + // Single byte ASCII... + *(*bufptr)++ = ch; + } + else if (ch < 0x800) + { + // Two-byte UTF-8... + *(*bufptr)++ = 0xc0 | (ch >> 6); + *(*bufptr)++ = 0x80 | (ch & 0x3f); + } + else if (ch < 0x10000) + { + // Three-byte UTF-8... + *(*bufptr)++ = 0xe0 | (ch >> 12); + *(*bufptr)++ = 0x80 | ((ch >> 6) & 0x3f); + *(*bufptr)++ = 0x80 | (ch & 0x3f); + } + else + { + // Four-byte UTF-8... + *(*bufptr)++ = 0xf0 | (ch >> 18); + *(*bufptr)++ = 0x80 | ((ch >> 12) & 0x3f); + *(*bufptr)++ = 0x80 | ((ch >> 6) & 0x3f); + *(*bufptr)++ = 0x80 | (ch & 0x3f); + } + + return (true); +} + + +// +// 'mxml_get_entity()' - Get the character corresponding to an entity... +// + +static int // O - Character value or `EOF` on error +mxml_get_entity( + mxml_options_t *options, // I - Options + mxml_io_cb_t io_cb, // I - Read callback function + void *io_cbdata, // I - Read callback data + _mxml_encoding_t *encoding, // IO - Character encoding + mxml_node_t *parent, // I - Parent node + int *line) // IO - Current line number +{ + int ch; // Current character + char entity[64], // Entity string + *entptr; // Pointer into entity + + + // Read a HTML character entity of the form "&NAME;", "&#NUMBER;", or "&#xHEX"... + entptr = entity; + + while ((ch = mxml_getc(options, io_cb, io_cbdata, encoding)) != EOF) + { + if (ch > 126 || (!isalnum(ch) && ch != '#')) + { + break; + } + else if (entptr < (entity + sizeof(entity) - 1)) + { + *entptr++ = ch; + } + else + { + _mxml_error(options, "Entity name too long under parent <%s> on line %d.", mxmlGetElement(parent), *line); + break; + } + } + + *entptr = '\0'; + + if (ch != ';') + { + _mxml_error(options, "Character entity '%s' not terminated under parent <%s> on line %d.", entity, mxmlGetElement(parent), *line); + + if (ch == '\n') + (*line)++; + + return (EOF); + } + + if ((ch = _mxml_entity_value(options, entity)) < 0) + { + _mxml_error(options, "Entity '&%s;' not supported under parent <%s> on line %d.", entity, mxmlGetElement(parent), *line); + return (EOF); + } + + if (mxml_bad_char(ch)) + { + _mxml_error(options, "Bad control character 0x%02x under parent <%s> on line %d not allowed by XML standard.", ch, mxmlGetElement(parent), *line); + return (EOF); + } + + return (ch); +} + + +// +// 'mxml_getc()' - Read a character from a file descriptor. +// + +static int // O - Character or `EOF` +mxml_getc(mxml_options_t *options, // I - Options + mxml_io_cb_t io_cb, // I - Read callback function + void *io_cbdata, // I - Read callback data + _mxml_encoding_t *encoding) // IO - Encoding +{ + int ch; // Current character + unsigned char buffer[4]; // Read buffer + + + // Grab the next character... + read_first_byte: + + if ((io_cb)(io_cbdata, buffer, 1) != 1) + return (EOF); + + ch = buffer[0]; + + switch (*encoding) + { + case _MXML_ENCODING_UTF8 : + // Got a UTF-8 character; convert UTF-8 to Unicode and return... + if (!(ch & 0x80)) + { + // ASCII + break; + } + else if (ch == 0xfe) + { + // UTF-16 big-endian BOM? + if ((io_cb)(io_cbdata, buffer + 1, 1) != 1) + return (EOF); + + if (buffer[1] != 0xff) + return (EOF); + + // Yes, switch to UTF-16 BE and try reading again... + *encoding = _MXML_ENCODING_UTF16BE; + + goto read_first_byte; + } + else if (ch == 0xff) + { + // UTF-16 little-endian BOM? + if ((io_cb)(io_cbdata, buffer + 1, 1) != 1) + return (EOF); + + if (buffer[1] != 0xfe) + return (EOF); + + // Yes, switch to UTF-16 LE and try reading again... + *encoding = _MXML_ENCODING_UTF16LE; + + goto read_first_byte; + } + else if ((ch & 0xe0) == 0xc0) + { + // Two-byte value... + if ((io_cb)(io_cbdata, buffer + 1, 1) != 1) + return (EOF); + + if ((buffer[1] & 0xc0) != 0x80) + return (EOF); + + ch = ((ch & 0x1f) << 6) | (buffer[1] & 0x3f); + + if (ch < 0x80) + { + _mxml_error(options, "Invalid UTF-8 sequence for character 0x%04x.", ch); + return (EOF); + } + } + else if ((ch & 0xf0) == 0xe0) + { + // Three-byte value... + if ((io_cb)(io_cbdata, buffer + 1, 2) != 2) + return (EOF); + + if ((buffer[1] & 0xc0) != 0x80 || (buffer[2] & 0xc0) != 0x80) + return (EOF); + + ch = ((ch & 0x0f) << 12) | ((buffer[1] & 0x3f) << 6) | (buffer[2] & 0x3f); + + if (ch < 0x800) + { + _mxml_error(options, "Invalid UTF-8 sequence for character 0x%04x.", ch); + return (EOF); + } + + // Ignore (strip) Byte Order Mark (BOM)... + if (ch == 0xfeff) + goto read_first_byte; + } + else if ((ch & 0xf8) == 0xf0) + { + // Four-byte value... + if ((io_cb)(io_cbdata, buffer + 1, 3) != 3) + return (EOF); + + if ((buffer[1] & 0xc0) != 0x80 || (buffer[2] & 0xc0) != 0x80 || (buffer[3] & 0xc0) != 0x80) + return (EOF); + + ch = ((ch & 0x07) << 18) | ((buffer[1] & 0x3f) << 12) | ((buffer[2] & 0x3f) << 6) | (buffer[3] & 0x3f); + + if (ch < 0x10000) + { + _mxml_error(options, "Invalid UTF-8 sequence for character 0x%04x.", ch); + return (EOF); + } + } + else + { + return (EOF); + } + break; + + case _MXML_ENCODING_UTF16BE : + // Read UTF-16 big-endian char... + if ((io_cb)(io_cbdata, buffer + 1, 1) != 1) + return (EOF); + + ch = (ch << 8) | buffer[1]; + + if (ch >= 0xd800 && ch <= 0xdbff) + { + // Multi-word UTF-16 char... + int lch; // Lower bits + + if ((io_cb)(io_cbdata, buffer + 2, 2) != 2) + return (EOF); + + lch = (buffer[2] << 8) | buffer[3]; + + if (lch < 0xdc00 || lch >= 0xdfff) + return (EOF); + + ch = (((ch & 0x3ff) << 10) | (lch & 0x3ff)) + 0x10000; + } + break; + + case _MXML_ENCODING_UTF16LE : + // Read UTF-16 little-endian char... + if ((io_cb)(io_cbdata, buffer + 1, 1) != 1) + return (EOF); + + ch |= buffer[1] << 8; + + if (ch >= 0xd800 && ch <= 0xdbff) + { + // Multi-word UTF-16 char... + int lch; // Lower bits + + if ((io_cb)(io_cbdata, buffer + 2, 2) != 2) + return (EOF); + + lch = (buffer[3] << 8) | buffer[2]; + + if (lch < 0xdc00 || lch >= 0xdfff) + return (EOF); + + ch = (((ch & 0x3ff) << 10) | (lch & 0x3ff)) + 0x10000; + } + break; + } + + if (mxml_bad_char(ch)) + { + _mxml_error(options, "Bad control character 0x%02x not allowed by XML standard.", ch); + return (EOF); + } + + return (ch); +} + + +// +// 'mxml_load_data()' - Load data into an XML node tree. +// + +static mxml_node_t * // O - First node or `NULL` if the XML could not be read. +mxml_load_data( + mxml_node_t *top, // I - Top node + mxml_options_t *options, // I - Options + mxml_io_cb_t io_cb, // I - Read callback function + void *io_cbdata) // I - Read callback data +{ + mxml_node_t *node = NULL, // Current node + *first = NULL, // First node added + *parent = NULL; // Current parent node + int line = 1, // Current line number + ch; // Character from file + bool whitespace = false; // Whitespace seen? + char *buffer, // String buffer + *bufptr; // Pointer into buffer + size_t bufsize; // Size of buffer + mxml_type_t type; // Current node type + _mxml_encoding_t encoding = _MXML_ENCODING_UTF8; + // Character encoding + static const char * const types[] = // Type strings... + { + "MXML_TYPE_CDATA", // CDATA + "MXML_TYPE_COMMENT", // Comment + "MXML_TYPE_DECLARATION",// Declaration + "MXML_TYPE_DIRECTIVE",// Processing instruction/directive + "MXML_TYPE_ELEMENT", // XML element with attributes + "MXML_TYPE_INTEGER", // Integer value + "MXML_TYPE_OPAQUE", // Opaque string + "MXML_TYPE_REAL", // Real value + "MXML_TYPE_TEXT", // Text fragment + "MXML_TYPE_CUSTOM" // Custom data + }; + + + // Read elements and other nodes from the file... + if ((buffer = malloc(64)) == NULL) + { + _mxml_error(options, "Unable to allocate string buffer."); + return (NULL); + } + + bufsize = 64; + bufptr = buffer; + parent = top; + first = NULL; + + if (options && options->type_cb && parent) + type = (options->type_cb)(options->type_cbdata, parent); + else if (options && !options->type_cb) + type = options->type_value; + else + type = MXML_TYPE_IGNORE; + + if ((ch = mxml_getc(options, io_cb, io_cbdata, &encoding)) == EOF) + { + free(buffer); + return (NULL); + } + else if (ch != '<' && !top) + { + free(buffer); + _mxml_error(options, "XML does not start with '<' (saw '%c').", ch); + return (NULL); + } + + do + { + if ((ch == '<' || (mxml_isspace(ch) && type != MXML_TYPE_OPAQUE && type != MXML_TYPE_CUSTOM)) && bufptr > buffer) + { + // Add a new value node... + *bufptr = '\0'; + + switch (type) + { + case MXML_TYPE_INTEGER : + node = mxmlNewInteger(parent, strtol(buffer, &bufptr, 0)); + break; + + case MXML_TYPE_OPAQUE : + node = mxmlNewOpaque(parent, buffer); + break; + + case MXML_TYPE_REAL : + node = mxmlNewReal(parent, mxml_strtod(options, buffer, &bufptr)); + break; + + case MXML_TYPE_TEXT : + node = mxmlNewText(parent, whitespace, buffer); + break; + + case MXML_TYPE_CUSTOM : + if (options && options->custload_cb) + { + // Use the callback to fill in the custom data... + node = mxmlNewCustom(parent, /*data*/NULL, /*free_cb*/NULL, /*free_cbdata*/NULL); + + if (!(options->custload_cb)(options->cust_cbdata, node, buffer)) + { + _mxml_error(options, "Bad custom value '%s' in parent <%s> on line %d.", buffer, parent ? parent->value.element.name : "null", line); + mxmlDelete(node); + node = NULL; + } + break; + } + + default : // Ignore... + node = NULL; + break; + } + + if (*bufptr) + { + // Bad integer/real number value... + _mxml_error(options, "Bad %s value '%s' in parent <%s> on line %d.", type == MXML_TYPE_INTEGER ? "integer" : "real", buffer, parent ? parent->value.element.name : "null", line); + break; + } + + MXML_DEBUG("mxml_load_data: node=%p(%s), parent=%p\n", node, buffer, parent); + + bufptr = buffer; + whitespace = mxml_isspace(ch) && type == MXML_TYPE_TEXT; + + if (!node && type != MXML_TYPE_IGNORE) + { + // Print error and return... + _mxml_error(options, "Unable to add value node of type %s to parent <%s> on line %d.", types[type], parent ? parent->value.element.name : "null", line); + goto error; + } + + if (options && options->sax_cb) + { + if (!(options->sax_cb)(options->sax_cbdata, node, MXML_SAX_EVENT_DATA)) + goto error; + + if (!mxmlRelease(node)) + node = NULL; + } + + if (!first && node) + first = node; + } + else if (mxml_isspace(ch) && type == MXML_TYPE_TEXT) + { + whitespace = true; + } + + if (ch == '\n') + line ++; + + // Add lone whitespace node if we have an element and existing whitespace... + if (ch == '<' && whitespace && type == MXML_TYPE_TEXT) + { + if (parent) + { + node = mxmlNewText(parent, whitespace, ""); + + if (options && options->sax_cb) + { + if (!(options->sax_cb)(options->sax_cbdata, node, MXML_SAX_EVENT_DATA)) + goto error; + + if (!mxmlRelease(node)) + node = NULL; + } + + if (!first && node) + first = node; + } + + whitespace = false; + } + + if (ch == '<') + { + // Start of open/close tag... + bufptr = buffer; + + while ((ch = mxml_getc(options, io_cb, io_cbdata, &encoding)) != EOF) + { + if (mxml_isspace(ch) || ch == '>' || (ch == '/' && bufptr > buffer)) + { + break; + } + else if (ch == '<') + { + _mxml_error(options, "Bare < in element."); + goto error; + } + else if (ch == '&') + { + if ((ch = mxml_get_entity(options, io_cb, io_cbdata, &encoding, parent, &line)) == EOF) + goto error; + + if (!mxml_add_char(options, ch, &bufptr, &buffer, &bufsize)) + goto error; + } + else if (ch < '0' && ch != '!' && ch != '-' && ch != '.' && ch != '/') + { + goto error; + } + else if (!mxml_add_char(options, ch, &bufptr, &buffer, &bufsize)) + { + goto error; + } + else if (((bufptr - buffer) == 1 && buffer[0] == '?') || ((bufptr - buffer) == 3 && !strncmp(buffer, "!--", 3)) || ((bufptr - buffer) == 8 && !strncmp(buffer, "![CDATA[", 8))) + { + break; + } + + if (ch == '\n') + line ++; + } + + *bufptr = '\0'; + + if (!strcmp(buffer, "!--")) + { + // Gather rest of comment... + while ((ch = mxml_getc(options, io_cb, io_cbdata, &encoding)) != EOF) + { + if (ch == '>' && bufptr > (buffer + 4) && bufptr[-3] != '-' && bufptr[-2] == '-' && bufptr[-1] == '-') + break; + else if (!mxml_add_char(options, ch, &bufptr, &buffer, &bufsize)) + goto error; + + if (ch == '\n') + line ++; + } + + // Error out if we didn't get the whole comment... + if (ch != '>') + { + // Print error and return... + _mxml_error(options, "Early EOF in comment node on line %d.", line); + goto error; + } + + // Otherwise add this as an element under the current parent... + bufptr[-2] = '\0'; + + if (!parent && first) + { + // There can only be one root element! + _mxml_error(options, "<%s--> cannot be a second root node after <%s> on line %d.", buffer, first->value.element.name, line); + goto error; + } + + if ((node = mxmlNewComment(parent, buffer + 3)) == NULL) + { + // Just print error for now... + _mxml_error(options, "Unable to add comment node to parent <%s> on line %d.", parent ? parent->value.element.name : "null", line); + break; + } + + MXML_DEBUG("mxml_load_data: node=%p(<%s-->), parent=%p\n", node, buffer, parent); + + if (options && options->sax_cb) + { + if (!(options->sax_cb)(options->sax_cbdata, node, MXML_SAX_EVENT_COMMENT)) + goto error; + + if (!mxmlRelease(node)) + node = NULL; + } + + if (node && !first) + first = node; + } + else if (!strcmp(buffer, "![CDATA[")) + { + // Gather CDATA section... + while ((ch = mxml_getc(options, io_cb, io_cbdata, &encoding)) != EOF) + { + if (ch == '>' && !strncmp(bufptr - 2, "]]", 2)) + { + // Drop terminator from CDATA string... + bufptr[-2] = '\0'; + break; + } + else if (!mxml_add_char(options, ch, &bufptr, &buffer, &bufsize)) + { + goto error; + } + + if (ch == '\n') + line ++; + } + + // Error out if we didn't get the whole comment... + if (ch != '>') + { + // Print error and return... + _mxml_error(options, "Early EOF in CDATA node on line %d.", line); + goto error; + } + + // Otherwise add this as an element under the current parent... + bufptr[-2] = '\0'; + + if (!parent && first) + { + // There can only be one root element! + _mxml_error(options, "<%s]]> cannot be a second root node after <%s> on line %d.", buffer, first->value.element.name, line); + goto error; + } + + if ((node = mxmlNewCDATA(parent, buffer + 8)) == NULL) + { + // Print error and return... + _mxml_error(options, "Unable to add CDATA node to parent <%s> on line %d.", parent ? parent->value.element.name : "null", line); + goto error; + } + + MXML_DEBUG("mxml_load_data: node=%p(<%s]]>), parent=%p\n", node, buffer, parent); + + if (options && options->sax_cb) + { + if (!(options->sax_cb)(options->sax_cbdata, node, MXML_SAX_EVENT_CDATA)) + goto error; + + if (!mxmlRelease(node)) + node = NULL; + } + + if (node && !first) + first = node; + } + else if (buffer[0] == '?') + { + // Gather rest of processing instruction... + while ((ch = mxml_getc(options, io_cb, io_cbdata, &encoding)) != EOF) + { + if (ch == '>' && bufptr > buffer && bufptr[-1] == '?') + break; + else if (!mxml_add_char(options, ch, &bufptr, &buffer, &bufsize)) + goto error; + + if (ch == '\n') + line ++; + } + + // Error out if we didn't get the whole processing instruction... + if (ch != '>') + { + // Print error and return... + _mxml_error(options, "Early EOF in processing instruction node on line %d.", line); + goto error; + } + + // Otherwise add this as an element under the current parent... + bufptr[-1] = '\0'; + + if (!parent && first) + { + // There can only be one root element! + _mxml_error(options, "<%s?> cannot be a second root node after <%s> on line %d.", buffer, first->value.element.name, line); + goto error; + } + + if ((node = mxmlNewDirective(parent, buffer + 1)) == NULL) + { + // Print error and return... + _mxml_error(options, "Unable to add processing instruction node to parent <%s> on line %d.", parent ? parent->value.element.name : "null", line); + goto error; + } + + MXML_DEBUG("mxml_load_data: node=%p(<%s?>), parent=%p\n", node, buffer, parent); + + if (options && options->sax_cb) + { + if (!(options->sax_cb)(options->sax_cbdata, node, MXML_SAX_EVENT_DIRECTIVE)) + goto error; + + if (strncmp(node->value.directive, "xml ", 4) && !mxmlRelease(node)) + node = NULL; + } + + if (node) + { + if (!first) + first = node; + + if (!parent) + { + parent = node; + + if (options && options->type_cb) + type = (options->type_cb)(options->type_cbdata, parent); + else if (options) + type = options->type_value; + else + type = MXML_TYPE_TEXT; + } + } + } + else if (buffer[0] == '!') + { + // Gather rest of declaration... + do + { + if (ch == '>') + { + break; + } + else + { + if (ch == '&') + { + if ((ch = mxml_get_entity(options, io_cb, io_cbdata, &encoding, parent, &line)) == EOF) + goto error; + } + + if (!mxml_add_char(options, ch, &bufptr, &buffer, &bufsize)) + goto error; + } + + if (ch == '\n') + line ++; + } + while ((ch = mxml_getc(options, io_cb, io_cbdata, &encoding)) != EOF); + + // Error out if we didn't get the whole declaration... + if (ch != '>') + { + // Print error and return... + _mxml_error(options, "Early EOF in declaration node on line %d.", line); + goto error; + } + + // Otherwise add this as an element under the current parent... + *bufptr = '\0'; + + if (!parent && first) + { + // There can only be one root element! + _mxml_error(options, "<%s> cannot be a second root node after <%s> on line %d.", buffer, first->value.element.name, line); + goto error; + } + + if ((node = mxmlNewDeclaration(parent, buffer + 1)) == NULL) + { + // Print error and return... + _mxml_error(options, "Unable to add declaration node to parent <%s> on line %d.", parent ? parent->value.element.name : "null", line); + goto error; + } + + MXML_DEBUG("mxml_load_data: node=%p(<%s>), parent=%p\n", node, buffer, parent); + + if (options && options->sax_cb) + { + if (!(options->sax_cb)(options->sax_cbdata, node, MXML_SAX_EVENT_DECLARATION)) + goto error; + + if (!mxmlRelease(node)) + node = NULL; + } + + if (node) + { + if (!first) + first = node; + + if (!parent) + { + parent = node; + + if (options && options->type_cb) + type = (options->type_cb)(options->type_cbdata, parent); + else if (options) + type = options->type_value; + else + type = MXML_TYPE_TEXT; + } + } + } + else if (buffer[0] == '/') + { + // Handle close tag... + MXML_DEBUG("mxml_load_data: <%s>, parent=%p\n", buffer, parent); + + if (!parent || strcmp(buffer + 1, parent->value.element.name)) + { + // Close tag doesn't match tree; print an error for now... + _mxml_error(options, "Mismatched close tag <%s> under parent <%s> on line %d.", buffer, parent ? parent->value.element.name : "(null)", line); + goto error; + } + + // Keep reading until we see >... + while (ch != '>' && ch != EOF) + ch = mxml_getc(options, io_cb, io_cbdata, &encoding); + + node = parent; + parent = parent->parent; + + if (options && options->sax_cb) + { + if (!(options->sax_cb)(options->sax_cbdata, node, MXML_SAX_EVENT_ELEMENT_CLOSE)) + goto error; + + if (!mxmlRelease(node)) + { + if (first == node) + first = NULL; + + node = NULL; + } + } + + // Ascend into the parent and set the value type as needed... + if (options && options->type_cb && parent) + type = (options->type_cb)(options->type_cbdata, parent); + else if (options && !options->type_cb) + type = options->type_value; + } + else + { + // Handle open tag... + if (!parent && first) + { + // There can only be one root element! + _mxml_error(options, "<%s> cannot be a second root node after <%s> on line %d.", buffer, first->value.element.name, line); + goto error; + } + + if ((node = mxmlNewElement(parent, buffer)) == NULL) + { + // Just print error for now... + _mxml_error(options, "Unable to add element node to parent <%s> on line %d.", parent ? parent->value.element.name : "null", line); + goto error; + } + + if (mxml_isspace(ch)) + { + MXML_DEBUG("mxml_load_data: node=%p(<%s...>), parent=%p\n", node, buffer, parent); + + if ((ch = mxml_parse_element(options, io_cb, io_cbdata, node, &encoding, &line)) == EOF) + goto error; + } + else if (ch == '/') + { + MXML_DEBUG("mxml_load_data: node=%p(<%s/>), parent=%p\n", node, buffer, parent); + + if ((ch = mxml_getc(options, io_cb, io_cbdata, &encoding)) != '>') + { + _mxml_error(options, "Expected > but got '%c' instead for element <%s/> on line %d.", ch, buffer, line); + mxmlDelete(node); + node = NULL; + goto error; + } + + ch = '/'; + } + + if (options && options->sax_cb) + { + if (!(options->sax_cb)(options->sax_cbdata, node, MXML_SAX_EVENT_ELEMENT_OPEN)) + goto error; + } + + if (!first) + first = node; + + if (ch == EOF) + break; + + if (ch != '/') + { + // Descend into this node, setting the value type as needed... + parent = node; + + if (options && options->type_cb && parent) + type = (options->type_cb)(options->type_cbdata, parent); + else if (options && !options->type_cb) + type = options->type_value; + else + type = MXML_TYPE_TEXT; + } + else if (options && options->sax_cb) + { + if (!(options->sax_cb)(options->sax_cbdata, node, MXML_SAX_EVENT_ELEMENT_CLOSE)) + goto error; + + if (!mxmlRelease(node)) + { + if (first == node) + first = NULL; + + node = NULL; + } + } + } + + bufptr = buffer; + } + else if (ch == '&') + { + // Add character entity to current buffer... + if ((ch = mxml_get_entity(options, io_cb, io_cbdata, &encoding, parent, &line)) == EOF) + goto error; + + if (!mxml_add_char(options, ch, &bufptr, &buffer, &bufsize)) + goto error; + } + else if (type == MXML_TYPE_OPAQUE || type == MXML_TYPE_CUSTOM || !mxml_isspace(ch)) + { + // Add character to current buffer... + if (!mxml_add_char(options, ch, &bufptr, &buffer, &bufsize)) + goto error; + } + } + while ((ch = mxml_getc(options, io_cb, io_cbdata, &encoding)) != EOF); + + // Free the string buffer - we don't need it anymore... + free(buffer); + + // Find the top element and return it... + if (parent) + { + node = parent; + + while (parent != top && parent->parent) + parent = parent->parent; + + if (node != parent) + { + _mxml_error(options, "Missing close tag under parent <%s> on line %d.", mxmlGetElement(node), node->parent ? node->parent->value.element.name : "(null)", line); + + mxmlDelete(first); + + return (NULL); + } + } + + if (parent) + return (parent); + else + return (first); + + // Common error return... + error: + + mxmlDelete(first); + + free(buffer); + + return (NULL); +} + + +// +// 'mxml_parse_element()' - Parse an element for any attributes... +// + +static int // O - Terminating character +mxml_parse_element( + mxml_options_t *options, // I - Options + mxml_io_cb_t io_cb, // I - Read callback function + void *io_cbdata, // I - Read callback data + mxml_node_t *node, // I - Element node + _mxml_encoding_t *encoding, // IO - Encoding + int *line) // IO - Current line number +{ + int ch, // Current character in file + quote; // Quoting character + char *name, // Attribute name + *value, // Attribute value + *ptr; // Pointer into name/value + size_t namesize, // Size of name string + valsize; // Size of value string + + + // Initialize the name and value buffers... + if ((name = malloc(64)) == NULL) + { + _mxml_error(options, "Unable to allocate memory for name."); + return (EOF); + } + + namesize = 64; + + if ((value = malloc(64)) == NULL) + { + free(name); + _mxml_error(options, "Unable to allocate memory for value."); + return (EOF); + } + + valsize = 64; + + // Loop until we hit a >, /, ?, or EOF... + while ((ch = mxml_getc(options, io_cb, io_cbdata, encoding)) != EOF) + { + MXML_DEBUG("mxml_parse_element: ch='%c'\n", ch); + + // Skip leading whitespace... + if (mxml_isspace(ch)) + { + if (ch == '\n') + (*line)++; + + continue; + } + + // Stop at /, ?, or >... + if (ch == '/' || ch == '?') + { + // Grab the > character and print an error if it isn't there... + quote = mxml_getc(options, io_cb, io_cbdata, encoding); + + if (quote != '>') + { + _mxml_error(options, "Expected '>' after '%c' for element %s, but got '%c' on line %d.", ch, mxmlGetElement(node), quote, *line); + goto error; + } + + break; + } + else if (ch == '<') + { + _mxml_error(options, "Bare < in element %s on line %d.", mxmlGetElement(node), *line); + goto error; + } + else if (ch == '>') + { + break; + } + + // Read the attribute name... + ptr = name; + if (!mxml_add_char(options, ch, &ptr, &name, &namesize)) + goto error; + + if (ch == '\"' || ch == '\'') + { + // Name is in quotes, so get a quoted string... + quote = ch; + + while ((ch = mxml_getc(options, io_cb, io_cbdata, encoding)) != EOF) + { + if (ch == '&') + { + if ((ch = mxml_get_entity(options, io_cb, io_cbdata, encoding, node, line)) == EOF) + goto error; + } + else if (ch == '\n') + { + (*line)++; + } + + if (!mxml_add_char(options, ch, &ptr, &name, &namesize)) + goto error; + + if (ch == quote) + break; + } + } + else + { + // Grab an normal, non-quoted name... + while ((ch = mxml_getc(options, io_cb, io_cbdata, encoding)) != EOF) + { + if (mxml_isspace(ch) || ch == '=' || ch == '/' || ch == '>' || ch == '?') + { + if (ch == '\n') + (*line)++; + break; + } + else + { + if (ch == '&') + { + if ((ch = mxml_get_entity(options, io_cb, io_cbdata, encoding, node, line)) == EOF) + goto error; + } + + if (!mxml_add_char(options, ch, &ptr, &name, &namesize)) + goto error; + } + } + } + + *ptr = '\0'; + + if (mxmlElementGetAttr(node, name)) + { + _mxml_error(options, "Duplicate attribute '%s' in element %s on line %d.", name, mxmlGetElement(node), *line); + goto error; + } + + while (ch != EOF && mxml_isspace(ch)) + { + ch = mxml_getc(options, io_cb, io_cbdata, encoding); + + if (ch == '\n') + (*line)++; + } + + if (ch == '=') + { + // Read the attribute value... + while ((ch = mxml_getc(options, io_cb, io_cbdata, encoding)) != EOF && mxml_isspace(ch)) + { + if (ch == '\n') + (*line)++; + } + + if (ch == EOF) + { + _mxml_error(options, "Missing value for attribute '%s' in element %s on line %d.", name, mxmlGetElement(node), *line); + goto error; + } + + if (ch == '\'' || ch == '\"') + { + // Read quoted value... + quote = ch; + ptr = value; + + while ((ch = mxml_getc(options, io_cb, io_cbdata, encoding)) != EOF) + { + if (ch == quote) + { + break; + } + else + { + if (ch == '&') + { + if ((ch = mxml_get_entity(options, io_cb, io_cbdata, encoding, node, line)) == EOF) + goto error; + } + else if (ch == '\n') + { + (*line)++; + } + + if (!mxml_add_char(options, ch, &ptr, &value, &valsize)) + goto error; + } + } + + *ptr = '\0'; + } + else + { + // Read unquoted value... + ptr = value; + if (!mxml_add_char(options, ch, &ptr, &value, &valsize)) + goto error; + + while ((ch = mxml_getc(options, io_cb, io_cbdata, encoding)) != EOF) + { + if (mxml_isspace(ch) || ch == '=' || ch == '/' || ch == '>') + { + if (ch == '\n') + (*line)++; + + break; + } + else + { + if (ch == '&') + { + if ((ch = mxml_get_entity(options, io_cb, io_cbdata, encoding, node, line)) == EOF) + goto error; + } + + if (!mxml_add_char(options, ch, &ptr, &value, &valsize)) + goto error; + } + } + + *ptr = '\0'; + } + + // Set the attribute with the given string value... + mxmlElementSetAttr(node, name, value); + MXML_DEBUG("mxml_parse_element: %s=\"%s\"\n", name, value); + } + else + { + _mxml_error(options, "Missing value for attribute '%s' in element %s on line %d.", name, mxmlGetElement(node), *line); + goto error; + } + + // Check the end character... + if (ch == '/' || ch == '?') + { + // Grab the > character and print an error if it isn't there... + quote = mxml_getc(options, io_cb, io_cbdata, encoding); + + if (quote != '>') + { + _mxml_error(options, "Expected '>' after '%c' for element %s, but got '%c' on line %d.", ch, mxmlGetElement(node), quote, *line); + ch = EOF; + } + + break; + } + else if (ch == '>') + break; + } + + // Free the name and value buffers and return... + free(name); + free(value); + + return (ch); + + // Common error return point... + error: + + free(name); + free(value); + + return (EOF); +} + + +// +// 'mxml_read_cb_fd()' - Read bytes from a file descriptor. +// + +// static size_t // O - Bytes read +// mxml_read_cb_fd(int *fd, // I - File descriptor +// void *buffer, // I - Buffer +// size_t bytes) // I - Bytes to read +// { +// #if _WIN32 +// int rbytes; // Bytes read + + +// rbytes = read(*fd, buffer, bytes); + +// #else +// ssize_t rbytes; // Bytes read + + +// while ((rbytes = read(*fd, buffer, bytes)) < 0) +// { +// if (errno != EINTR && errno != EAGAIN) +// break; +// } +// #endif // _WIN32 + +// if (rbytes < 0) +// return (0); +// else +// return ((size_t)rbytes); +// } + + +// +// 'mxml_read_cb_file()' - Read bytes from a file pointer. +// + +static size_t // O - Bytes read +mxml_read_cb_file(FILE *fp, // I - File pointer + void *buffer, // I - Buffer + size_t bytes) // I - Bytes to read +{ + if (feof(fp)) + return (0); + else + return (fread(buffer, 1, bytes, fp)); +} + + +// +// 'mxml_read_cb_string()' - Read bytes from a string. +// + +static size_t // O - Bytes read +mxml_read_cb_string( + _mxml_stringbuf_t *sb, // I - String buffer + void *buffer, // I - Buffer + size_t bytes) // I - Bytes to read +{ + size_t remaining; // Remaining bytes in buffer + + + if ((remaining = sb->bufsize - (size_t)(sb->bufptr - sb->buffer)) < bytes) + bytes = remaining; + + if (bytes > 0) + { + // Copy bytes from string... + memcpy(buffer, sb->bufptr, bytes); + sb->bufptr += bytes; + } + + return (bytes); +} + + +// +// 'mxml_strtod()' - Convert a string to a double without respect to the locale. +// + +static double // O - Real number +mxml_strtod(mxml_options_t *options, // I - Options + const char *buffer, // I - String + char **bufend) // O - End of number in string +{ + const char *bufptr; // Pointer into buffer + char temp[64], // Temporary buffer + *tempptr; // Pointer into temporary buffer + + + // See if the locale has a special decimal point string... + if (!options || !options->loc) + return (strtod(buffer, bufend)); + + // Copy leading sign, numbers, period, and then numbers... + tempptr = temp; + temp[sizeof(temp) - 1] = '\0'; + bufptr = buffer; + + if (*bufptr == '-' || *bufptr == '+') + *tempptr++ = *bufptr++; + + while (*bufptr && isdigit(*bufptr & 255)) + { + if (tempptr < (temp + sizeof(temp) - 1)) + { + *tempptr++ = *bufptr++; + } + else + { + *bufend = (char *)bufptr; + return (0.0); + } + } + + if (*bufptr == '.') + { + // Convert decimal point to locale equivalent... + size_t declen = strlen(options->loc->decimal_point); + // Length of decimal point + bufptr ++; + + if (declen <= (sizeof(temp) - (size_t)(tempptr - temp))) + { + memcpy(tempptr, options->loc->decimal_point, declen); + tempptr += declen; + } + else + { + *bufend = (char *)bufptr; + return (0.0); + } + } + + // Copy any remaining characters... + while (*bufptr && isdigit(*bufptr & 255)) + { + if (tempptr < (temp + sizeof(temp) - 1)) + *tempptr++ = *bufptr++; + else + break; + } + + *bufend = (char *)bufptr; + + if (*bufptr) + return (0.0); + + // Nul-terminate the temporary string and convert the string... + *tempptr = '\0'; + + return (strtod(temp, NULL)); +} + + +// +// 'mxml_io_cb_fd()' - Write bytes to a file descriptor. +// + +// static size_t // O - Bytes written +// mxml_io_cb_fd(int *fd, // I - File descriptor +// void *buffer, // I - Buffer +// size_t bytes) // I - Bytes to write +// { +// #if _WIN32 +// int wbytes; // Bytes written + + +// wbytes = write(*fd, buffer, bytes); + +// #else +// ssize_t wbytes; // Bytes written + + +// while ((wbytes = write(*fd, buffer, bytes)) < 0) +// { +// if (errno != EINTR && errno != EAGAIN) +// break; +// } +// #endif // _WIN32 + +// if (wbytes < 0) +// return (0); +// else +// return ((size_t)wbytes); +// } + + +// +// 'mxml_io_cb_file()' - Write bytes to a file pointer. +// + +static size_t // O - Bytes written +mxml_io_cb_file(FILE *fp, // I - File pointer + void *buffer, // I - Buffer + size_t bytes) // I - Bytes to write +{ + return (fwrite(buffer, 1, bytes, fp)); +} + + +// +// 'mxml_io_cb_string()' - Write bytes to a string buffer. +// + +static size_t // O - Bytes written +mxml_io_cb_string( + _mxml_stringbuf_t *sb, // I - String buffer + void *buffer, // I - Buffer + size_t bytes) // I - Bytes to write +{ + size_t remaining; // Remaining bytes + + + // Expand buffer as needed... + if ((sb->bufptr + bytes) >= (sb->buffer + sb->bufsize - 1) && sb->bufalloc) + { + // Reallocate buffer + char *temp; // New buffer pointer + size_t newsize; // New bufsize + + newsize = (size_t)(sb->bufptr - sb->buffer) + bytes + 257; + if ((temp = realloc(sb->buffer, newsize)) == NULL) + return (0); + + sb->bufptr = temp + (sb->bufptr - sb->buffer); + sb->buffer = temp; + sb->bufsize = newsize; + } + + // Copy what we can... + if (sb->bufptr >= (sb->buffer + sb->bufsize - 1)) + return (0); // No more room + else if ((remaining = (sb->bufsize - (size_t)(sb->bufptr - sb->buffer) - 1)) < bytes) + bytes = remaining; + + memcpy(sb->bufptr, buffer, bytes); + sb->bufptr += bytes; + + return (bytes); +} + + +// +// 'mxml_write_node()' - Save an XML node to a file. +// + +static int // O - Column or -1 on error +mxml_write_node( + mxml_node_t *node, // I - Node to write + mxml_options_t *options, // I - Options + mxml_io_cb_t io_cb, // I - Write callback function + void *io_cbdata, // I - Write callback data + int col) // I - Current column +{ + mxml_node_t *current, // Current node + *next; // Next node + size_t i, // Looping var + width; // Width of attr + value + _mxml_attr_t *attr; // Current attribute + char s[255], // Temporary string + *data; // Custom data string + const char *text; // Text string + bool whitespace; // Whitespace before text string? + + + // Loop through this node and all of its children... + for (current = node; current && col >= 0; current = next) + { + // Print the node value... + MXML_DEBUG("mxml_write_node: current=%p(%d)\n", current, current->type); + + switch (mxmlGetType(current)) + { + case MXML_TYPE_CDATA : + col = mxml_write_ws(current, options, io_cb, io_cbdata, MXML_WS_BEFORE_OPEN, col); + col = mxml_write_string("", io_cb, io_cbdata, /*use_entities*/false, col); + col = mxml_write_ws(current, options, io_cb, io_cbdata, MXML_WS_AFTER_OPEN, col); + break; + + case MXML_TYPE_COMMENT : + col = mxml_write_ws(current, options, io_cb, io_cbdata, MXML_WS_BEFORE_OPEN, col); + col = mxml_write_string("", io_cb, io_cbdata, /*use_entities*/false, col); + col = mxml_write_ws(current, options, io_cb, io_cbdata, MXML_WS_AFTER_OPEN, col); + break; + + case MXML_TYPE_DECLARATION : + col = mxml_write_ws(current, options, io_cb, io_cbdata, MXML_WS_BEFORE_OPEN, col); + col = mxml_write_string("", io_cb, io_cbdata, /*use_entities*/false, col); + col = mxml_write_ws(current, options, io_cb, io_cbdata, MXML_WS_AFTER_OPEN, col); + break; + + case MXML_TYPE_DIRECTIVE : + col = mxml_write_ws(current, options, io_cb, io_cbdata, MXML_WS_BEFORE_OPEN, col); + col = mxml_write_string("", io_cb, io_cbdata, /*use_entities*/false, col); + col = mxml_write_ws(current, options, io_cb, io_cbdata, MXML_WS_AFTER_OPEN, col); + break; + + case MXML_TYPE_ELEMENT : + col = mxml_write_ws(current, options, io_cb, io_cbdata, MXML_WS_BEFORE_OPEN, col); + col = mxml_write_string("<", io_cb, io_cbdata, /*use_entities*/false, col); + col = mxml_write_string(mxmlGetElement(current), io_cb, io_cbdata, /*use_entities*/true, col); + + for (i = current->value.element.num_attrs, attr = current->value.element.attrs; i > 0 && col >= 0; i --, attr ++) + { + width = strlen(attr->name); + + if (attr->value) + width += strlen(attr->value) + 3; + + if (options && options->wrap > 0 && (col + (int)width) > options->wrap) + col = mxml_write_string("\n", io_cb, io_cbdata, /*use_entities*/false, col); + else + col = mxml_write_string(" ", io_cb, io_cbdata, /*use_entities*/false, col); + + col = mxml_write_string(attr->name, io_cb, io_cbdata, /*use_entities*/true, col); + + if (attr->value) + { + col = mxml_write_string("=\"", io_cb, io_cbdata, /*use_entities*/false, col); + col = mxml_write_string(attr->value, io_cb, io_cbdata, /*use_entities*/true, col); + col = mxml_write_string("\"", io_cb, io_cbdata, /*use_entities*/false, col); + } + } + + col = mxml_write_string(current->child ? ">" : "/>", io_cb, io_cbdata, /*use_entities*/false, col); + col = mxml_write_ws(current, options, io_cb, io_cbdata, MXML_WS_AFTER_OPEN, col); + break; + + case MXML_TYPE_INTEGER : + if (current->prev) + { + // Add whitespace separator... + if (options && options->wrap > 0 && col > options->wrap) + col = mxml_write_string("\n", io_cb, io_cbdata, /*use_entities*/false, col); + else + col = mxml_write_string(" ", io_cb, io_cbdata, /*use_entities*/false, col); + } + + // Write integer... + snprintf(s, sizeof(s), "%ld", current->value.integer); + col = mxml_write_string(s, io_cb, io_cbdata, /*use_entities*/true, col); + break; + + case MXML_TYPE_OPAQUE : + col = mxml_write_string(mxmlGetOpaque(current), io_cb, io_cbdata, /*use_entities*/true, col); + break; + + case MXML_TYPE_REAL : + if (current->prev) + { + // Add whitespace separator... + if (options && options->wrap > 0 && col > options->wrap) + col = mxml_write_string("\n", io_cb, io_cbdata, /*use_entities*/false, col); + else + col = mxml_write_string(" ", io_cb, io_cbdata, /*use_entities*/false, col); + } + + // Write real number... + snprintf(s, sizeof(s), "%g", current->value.real); + + if (options && options->loc) + { + char *sptr; // Pointer into string + + if ((sptr = strstr(s, options->loc->decimal_point)) != NULL) + { + // Convert locale decimal point to "." + if (options->loc_declen > 1) + memmove(sptr + 1, sptr + options->loc_declen, strlen(sptr + options->loc_declen) + 1); + + *sptr = '.'; + } + } + + col = mxml_write_string(s, io_cb, io_cbdata, /*use_entities*/true, col); + break; + + case MXML_TYPE_TEXT : + text = mxmlGetText(current, &whitespace); + + if (whitespace && col > 0) + { + // Add whitespace separator... + if (options && options->wrap > 0 && col > options->wrap) + col = mxml_write_string("\n", io_cb, io_cbdata, /*use_entities*/false, col); + else + col = mxml_write_string(" ", io_cb, io_cbdata, /*use_entities*/false, col); + } + + col = mxml_write_string(text, io_cb, io_cbdata, /*use_entities*/true, col); + break; + + case MXML_TYPE_CUSTOM : + if (!options || !options->custsave_cb) + return (-1); + + if ((data = (options->custsave_cb)(options->cust_cbdata, current)) == NULL) + return (-1); + + col = mxml_write_string(data, io_cb, io_cbdata, /*use_entities*/true, col); + + free(data); + break; + + default : // Should never happen + return (-1); + } + + // Figure out the next node... + if ((next = mxmlGetFirstChild(current)) == NULL) + { + if (current == node) + { + // Don't traverse to sibling node if we are at the "root" node... + next = NULL; + } + else + { + // Try the next sibling, and continue traversing upwards as needed... + while ((next = mxmlGetNextSibling(current)) == NULL) + { + if (current == node || !mxmlGetParent(current)) + break; + + // Declarations and directives have no end tags... + current = mxmlGetParent(current); + + if (mxmlGetType(current) == MXML_TYPE_ELEMENT) + { + col = mxml_write_ws(current, options, io_cb, io_cbdata, MXML_WS_BEFORE_CLOSE, col); + col = mxml_write_string("", io_cb, io_cbdata, /*use_entities*/false, col); + col = mxml_write_ws(current, options, io_cb, io_cbdata, MXML_WS_AFTER_CLOSE, col); + } + + if (current == node) + break; + } + } + } + } + + return (col); +} + + +// +// 'mxml_write_string()' - Write a string, escaping & and < as needed. +// + +static int // O - New column or `-1` on error +mxml_write_string( + const char *s, // I - String to write + mxml_io_cb_t io_cb, // I - Write callback function + void *io_cbdata, // I - Write callback data + bool use_entities, // I - Escape special characters? + int col) // I - Current column +{ + const char *frag, // Start of current string fragment + *ptr, // Pointer into string + *ent; // Entity, if any + size_t fraglen; // Length of fragment + + + MXML_DEBUG("mxml_write_string(io_cb=%p, io_cbdata=%p, s=\"%s\", use_entities=%s, col=%d)\n", io_cb, io_cbdata, s, use_entities ? "true" : "false", col); + + if (col < 0) + return (-1); + + for (frag = ptr = s; *ptr; ptr ++) + { + if (use_entities && (ent = _mxml_entity_string(*ptr)) != NULL) + { + size_t entlen = strlen(ent); // Length of entity + + if (ptr > frag) + { + // Write current fragment + fraglen = (size_t)(ptr - frag); + + if ((io_cb)(io_cbdata, (char *)frag, fraglen) != fraglen) + return (-1); + } + + frag = ptr + 1; + + // Write entity + if ((io_cb)(io_cbdata, (char *)ent, entlen) != entlen) + return (-1); + + col ++; + } + else if (*ptr == '\r' || *ptr == '\n') + { + // CR or LF resets column + col = 0; + } + else if (*ptr == '\t') + { + // Tab indents column + col = col - (col % MXML_TAB) + MXML_TAB; + } + else + { + // All other characters are 1 column wide + col ++; + } + } + + if (ptr > frag) + { + // Write final fragment + fraglen = (size_t)(ptr - frag); + + if ((io_cb)(io_cbdata, (char *)frag, fraglen) != fraglen) + return (-1); + } + + return (col); +} + + +// +// 'mxml_write_ws()' - Do whitespace callback... +// + +static int // O - New column or `-1` on error +mxml_write_ws( + mxml_node_t *node, // I - Current node + mxml_options_t *options, // I - Options + mxml_io_cb_t io_cb, // I - Write callback function + void *io_cbdata, // I - Write callback data + mxml_ws_t ws, // I - Whitespace value + int col) // I - Current column +{ + const char *s; // Whitespace string + + + if (options && options->ws_cb && (s = (options->ws_cb)(options->ws_cbdata, node, ws)) != NULL) + col = mxml_write_string(s, io_cb, io_cbdata, /*use_entities*/false, col); + + return (col); +} diff --git a/src/main/mxml/mxml-get.c b/src/main/mxml/mxml-get.c new file mode 100644 index 0000000..384bc8d --- /dev/null +++ b/src/main/mxml/mxml-get.c @@ -0,0 +1,364 @@ +// +// Node get functions for Mini-XML, a small XML file parsing library. +// +// https://www.msweet.org/mxml +// +// Copyright © 2014-2024 by Michael R Sweet. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "mxml-private.h" + + +// +// 'mxmlGetCDATA()' - Get the value for a CDATA node. +// +// This function gets the string value of a CDATA node. `NULL` is returned if +// the node is not a CDATA element. +// + +const char * // O - CDATA value or `NULL` +mxmlGetCDATA(mxml_node_t *node) // I - Node to get +{ + // Range check input... + if (!node || node->type != MXML_TYPE_CDATA) + return (NULL); + + // Return the CDATA string... + return (node->value.cdata); +} + + +// +// 'mxmlGetComment()' - Get the value for a comment node. +// +// This function gets the string value of a comment node. `NULL` is returned +// if the node is not a comment. +// + +const char * // O - Comment value or `NULL` +mxmlGetComment(mxml_node_t *node) // I - Node to get +{ + // Range check input... + if (!node || node->type != MXML_TYPE_COMMENT) + return (NULL); + + // Return the comment string... + return (node->value.comment); +} + + +// +// 'mxmlGetCustom()' - Get the value for a custom node. +// +// This function gets the binary value of a custom node. `NULL` is returned if +// the node (or its first child) is not a custom value node. +// + +const void * // O - Custom value or `NULL` +mxmlGetCustom(mxml_node_t *node) // I - Node to get +{ + // Range check input... + if (!node) + return (NULL); + + // Return the custom value... + if (node->type == MXML_TYPE_CUSTOM) + return (node->value.custom.data); + else if (node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_CUSTOM) + return (node->child->value.custom.data); + else + return (NULL); +} + + +// +// 'mxmlGetDeclaration()' - Get the value for a declaration node. +// +// This function gets the string value of a declaraction node. `NULL` is +// returned if the node is not a declaration. +// + +const char * // O - Declaraction value or `NULL` +mxmlGetDeclaration(mxml_node_t *node) // I - Node to get +{ + // Range check input... + if (!node || node->type != MXML_TYPE_DECLARATION) + return (NULL); + + // Return the comment string... + return (node->value.declaration); +} + + +// +// 'mxmlGetDirective()' - Get the value for a processing instruction node. +// +// This function gets the string value of a processing instruction. `NULL` is +// returned if the node is not a processing instruction. +// + +const char * // O - Comment value or `NULL` +mxmlGetDirective(mxml_node_t *node) // I - Node to get +{ + // Range check input... + if (!node || node->type != MXML_TYPE_DIRECTIVE) + return (NULL); + + // Return the comment string... + return (node->value.directive); +} + + +// +// 'mxmlGetElement()' - Get the name for an element node. +// +// This function gets the name of an element node. `NULL` is returned if the +// node is not an element node. +// + +const char * // O - Element name or `NULL` +mxmlGetElement(mxml_node_t *node) // I - Node to get +{ + // Range check input... + if (!node || node->type != MXML_TYPE_ELEMENT) + return (NULL); + + // Return the element name... + return (node->value.element.name); +} + + +// +// 'mxmlGetFirstChild()' - Get the first child of a node. +// +// This function gets the first child of a node. `NULL` is returned if the node +// has no children. +// + +mxml_node_t * // O - First child or `NULL` +mxmlGetFirstChild(mxml_node_t *node) // I - Node to get +{ + // Return the first child node... + return (node ? node->child : NULL); +} + + +// +// 'mxmlGetInteger()' - Get the integer value from the specified node or its +// first child. +// +// This function gets the value of an integer node. `0` is returned if the node +// (or its first child) is not an integer value node. +// + +long // O - Integer value or `0` +mxmlGetInteger(mxml_node_t *node) // I - Node to get +{ + // Range check input... + if (!node) + return (0); + + // Return the integer value... + if (node->type == MXML_TYPE_INTEGER) + return (node->value.integer); + else if (node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_INTEGER) + return (node->child->value.integer); + else + return (0); +} + + +// +// 'mxmlGetLastChild()' - Get the last child of a node. +// +// This function gets the last child of a node. `NULL` is returned if the node +// has no children. +// + +mxml_node_t * // O - Last child or `NULL` +mxmlGetLastChild(mxml_node_t *node) // I - Node to get +{ + return (node ? node->last_child : NULL); +} + + +// +// 'mxmlGetNextSibling()' - Get the next node for the current parent. +// +// This function gets the next node for the current parent. `NULL` is returned +// if this is the last child for the current parent. +// + +mxml_node_t * +mxmlGetNextSibling(mxml_node_t *node) // I - Node to get +{ + return (node ? node->next : NULL); +} + + +// +// 'mxmlGetOpaque()' - Get an opaque string value for a node or its first child. +// +// This function gets the string value of an opaque node. `NULL` is returned if +// the node (or its first child) is not an opaque value node. +// + +const char * // O - Opaque string or `NULL` +mxmlGetOpaque(mxml_node_t *node) // I - Node to get +{ + // Range check input... + if (!node) + return (NULL); + + // Return the opaque value... + if (node->type == MXML_TYPE_OPAQUE) + return (node->value.opaque); + else if (node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_OPAQUE) + return (node->child->value.opaque); + else + return (NULL); +} + + +// +// 'mxmlGetParent()' - Get the parent node. +// +// This function gets the parent of a node. `NULL` is returned for a root node. +// + +mxml_node_t * // O - Parent node or `NULL` +mxmlGetParent(mxml_node_t *node) // I - Node to get +{ + return (node ? node->parent : NULL); +} + + +// +// 'mxmlGetPrevSibling()' - Get the previous node for the current parent. +// +// This function gets the previous node for the current parent. `NULL` is +// returned if this is the first child for the current parent. +// + +mxml_node_t * // O - Previous node or `NULL` +mxmlGetPrevSibling(mxml_node_t *node) // I - Node to get +{ + return (node ? node->prev : NULL); +} + + +// +// 'mxmlGetReal()' - Get the real value for a node or its first child. +// +// This function gets the value of a real value node. `0.0` is returned if the +// node (or its first child) is not a real value node. +// + +double // O - Real value or 0.0 +mxmlGetReal(mxml_node_t *node) // I - Node to get +{ + // Range check input... + if (!node) + return (0.0); + + // Return the real value... + if (node->type == MXML_TYPE_REAL) + return (node->value.real); + else if (node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_REAL) + return (node->child->value.real); + else + return (0.0); +} + + +// +// 'mxmlGetText()' - Get the text value for a node or its first child. +// +// This function gets the string and whitespace values of a text node. `NULL` +// and `false` are returned if the node (or its first child) is not a text node. +// The `whitespace` argument can be `NULL` if you don't want to know the +// whitespace value. +// +// Note: Text nodes consist of whitespace-delimited words. You will only get +// single words of text when reading an XML file with `MXML_TYPE_TEXT` nodes. +// If you want the entire string between elements in the XML file, you MUST read +// the XML file with `MXML_TYPE_OPAQUE` nodes and get the resulting strings +// using the @link mxmlGetOpaque@ function instead. +// + +const char * // O - Text string or `NULL` +mxmlGetText(mxml_node_t *node, // I - Node to get + bool *whitespace) // O - `true` if string is preceded by whitespace, `false` otherwise +{ + // Range check input... + if (!node) + { + if (whitespace) + *whitespace = false; + + return (NULL); + } + + // Return the integer value... + if (node->type == MXML_TYPE_TEXT) + { + if (whitespace) + *whitespace = node->value.text.whitespace; + + return (node->value.text.string); + } + else if (node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_TEXT) + { + if (whitespace) + *whitespace = node->child->value.text.whitespace; + + return (node->child->value.text.string); + } + else + { + if (whitespace) + *whitespace = false; + + return (NULL); + } +} + + +// +// 'mxmlGetType()' - Get the node type. +// +// This function gets the type of `node`. `MXML_TYPE_IGNORE` is returned if +// `node` is `NULL`. +// + +mxml_type_t // O - Type of node +mxmlGetType(mxml_node_t *node) // I - Node to get +{ + // Range check input... + if (!node) + return (MXML_TYPE_IGNORE); + + // Return the node type... + return (node->type); +} + + +// +// 'mxmlGetUserData()' - Get the user data pointer for a node. +// +// This function gets the user data pointer associated with `node`. +// + +void * // O - User data pointer +mxmlGetUserData(mxml_node_t *node) // I - Node to get +{ + // Range check input... + if (!node) + return (NULL); + + // Return the user data pointer... + return (node->user_data); +} diff --git a/src/main/mxml/mxml-index.c b/src/main/mxml/mxml-index.c new file mode 100644 index 0000000..9da244b --- /dev/null +++ b/src/main/mxml/mxml-index.c @@ -0,0 +1,418 @@ +// +// Index support code for Mini-XML, a small XML file parsing library. +// +// https://www.msweet.org/mxml +// +// Copyright © 2003-2024 by Michael R Sweet. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "mxml-private.h" + + +// +// Local functions... +// + +static int index_compare(mxml_index_t *ind, mxml_node_t *first, mxml_node_t *second); +static int index_find(mxml_index_t *ind, const char *element, const char *value, mxml_node_t *node); +static void index_sort(mxml_index_t *ind, int left, int right); + + +// +// 'mxmlIndexDelete()' - Delete an index. +// + +void +mxmlIndexDelete(mxml_index_t *ind) // I - Index to delete +{ + // Range check input.. + if (!ind) + return; + + // Free memory... + _mxml_strfree(ind->attr); + free(ind->nodes); + free(ind); +} + + +// +// 'mxmlIndexEnum()' - Return the next node in the index. +// +// This function returns the next node in index `ind`. +// +// You should call @link mxmlIndexReset@ prior to using this function to get +// the first node in the index. Nodes are returned in the sorted order of the +// index. +// + +mxml_node_t * // O - Next node or `NULL` if there is none +mxmlIndexEnum(mxml_index_t *ind) // I - Index to enumerate +{ + // Range check input... + if (!ind) + return (NULL); + + // Return the next node... + if (ind->cur_node < ind->num_nodes) + return (ind->nodes[ind->cur_node ++]); + else + return (NULL); +} + + +// +// 'mxmlIndexFind()' - Find the next matching node. +// +// This function finds the next matching node in index `ind`. +// +// You should call @link mxmlIndexReset@ prior to using this function for +// the first time with a particular set of `element` and `value` +// strings. Passing `NULL` for both `element` and `value` is equivalent +// to calling @link mxmlIndexEnum@. +// + +mxml_node_t * // O - Node or `NULL` if none found +mxmlIndexFind(mxml_index_t *ind, // I - Index to search + const char *element, // I - Element name to find, if any + const char *value) // I - Attribute value, if any +{ + int diff, // Difference between names + current, // Current entity in search + first, // First entity in search + last; // Last entity in search + + + MXML_DEBUG("mxmlIndexFind(ind=%p, element=\"%s\", value=\"%s\")\n", ind, element ? element : "(null)", value ? value : "(null)"); + + // Range check input... + if (!ind || (!ind->attr && value)) + { + MXML_DEBUG("mxmlIndexFind: Returning NULL, ind->attr=\"%s\"...\n", ind && ind->attr ? ind->attr : "(null)"); + return (NULL); + } + + // If both element and value are NULL, just enumerate the nodes in the index... + if (!element && !value) + return (mxmlIndexEnum(ind)); + + // If there are no nodes in the index, return NULL... + if (!ind->num_nodes) + { + MXML_DEBUG("mxmlIndexFind: Returning NULL, no nodes...\n"); + return (NULL); + } + + // If cur_node == 0, then find the first matching node... + if (ind->cur_node == 0) + { + // Find the first node using a modified binary search algorithm... + first = 0; + last = ind->num_nodes - 1; + + MXML_DEBUG("mxmlIndexFind: Find first time, num_nodes=%lu...\n", (unsigned long)ind->num_nodes); + + while ((last - first) > 1) + { + current = (first + last) / 2; + + MXML_DEBUG("mxmlIndexFind: first=%d, last=%d, current=%d\n", first, last, current); + + if ((diff = index_find(ind, element, value, ind->nodes[current])) == 0) + { + // Found a match, move back to find the first... + MXML_DEBUG("mxmlIndexFind: match.\n"); + + while (current > 0 && !index_find(ind, element, value, ind->nodes[current - 1])) + current --; + + MXML_DEBUG("mxmlIndexFind: Returning first match=%d\n", current); + + // Return the first match and save the index to the next... + ind->cur_node = current + 1; + + return (ind->nodes[current]); + } + else if (diff < 0) + { + last = current; + } + else + { + first = current; + } + + MXML_DEBUG("mxmlIndexFind: diff=%d\n", diff); + } + + // If we get this far, then we found exactly 0 or 1 matches... + for (current = first; current <= last; current ++) + { + if (!index_find(ind, element, value, ind->nodes[current])) + { + // Found exactly one (or possibly two) match... + MXML_DEBUG("mxmlIndexFind: Returning only match %d...\n", current); + ind->cur_node = current + 1; + + return (ind->nodes[current]); + } + } + + // No matches... + ind->cur_node = ind->num_nodes; + MXML_DEBUG("mxmlIndexFind: Returning NULL...\n"); + return (NULL); + } + else if (ind->cur_node < ind->num_nodes && !index_find(ind, element, value, ind->nodes[ind->cur_node])) + { + // Return the next matching node... + MXML_DEBUG("mxmlIndexFind: Returning next match %lu...\n", (unsigned long)ind->cur_node); + return (ind->nodes[ind->cur_node ++]); + } + + // If we get this far, then we have no matches... + ind->cur_node = ind->num_nodes; + + MXML_DEBUG("mxmlIndexFind: Returning NULL...\n"); + return (NULL); +} + + +// +// 'mxmlIndexGetCount()' - Get the number of nodes in an index. +// + +size_t // I - Number of nodes in index +mxmlIndexGetCount(mxml_index_t *ind) // I - Index of nodes +{ + // Range check input... + if (!ind) + return (0); + + // Return the number of nodes in the index... + return (ind->num_nodes); +} + + +// +// 'mxmlIndexNew()' - Create a new index. +// +// This function creates a new index for XML tree `node`. +// +// The index will contain all nodes that contain the named element and/or +// attribute. If both `element` and `attr` are `NULL`, then the index will +// contain a sorted list of the elements in the node tree. Nodes are +// sorted by element name and optionally by attribute value if the `attr` +// argument is not `NULL`. +// + +mxml_index_t * // O - New index +mxmlIndexNew(mxml_node_t *node, // I - XML node tree + const char *element, // I - Element to index or `NULL` for all + const char *attr) // I - Attribute to index or `NULL` for none +{ + mxml_index_t *ind; // New index + mxml_node_t *current, // Current node in index + **temp; // Temporary node pointer array + + + // Range check input... + MXML_DEBUG("mxmlIndexNew(node=%p, element=\"%s\", attr=\"%s\")\n", node, element ? element : "(null)", attr ? attr : "(null)"); + + if (!node) + return (NULL); + + // Create a new index... + if ((ind = calloc(1, sizeof(mxml_index_t))) == NULL) + return (NULL); + + if (attr) + { + if ((ind->attr = _mxml_strcopy(attr)) == NULL) + { + free(ind); + return (NULL); + } + } + + if (!element && !attr) + current = node; + else + current = mxmlFindElement(node, node, element, attr, NULL, MXML_DESCEND_ALL); + + while (current) + { + if (ind->num_nodes >= ind->alloc_nodes) + { + if ((temp = realloc(ind->nodes, (ind->alloc_nodes + MXML_ALLOC_SIZE) * sizeof(mxml_node_t *))) == NULL) + { + // Unable to allocate memory for the index, so abort... + mxmlIndexDelete(ind); + return (NULL); + } + + ind->nodes = temp; + ind->alloc_nodes += MXML_ALLOC_SIZE; + } + + ind->nodes[ind->num_nodes ++] = current; + + current = mxmlFindElement(current, node, element, attr, NULL, MXML_DESCEND_ALL); + } + + // Sort nodes based upon the search criteria... + if (ind->num_nodes > 1) + index_sort(ind, 0, ind->num_nodes - 1); + + // Return the new index... + return (ind); +} + + +// +// 'mxmlIndexReset()' - Reset the enumeration/find pointer in the index and +// return the first node in the index. +// +// This function resets the enumeration/find pointer in index `ind` and should +// be called prior to using @link mxmlIndexEnum@ or @link mxmlIndexFind@ for the +// first time. +// + +mxml_node_t * // O - First node or `NULL` if there is none +mxmlIndexReset(mxml_index_t *ind) // I - Index to reset +{ + MXML_DEBUG("mxmlIndexReset(ind=%p)\n", ind); + + // Range check input... + if (!ind) + return (NULL); + + // Set the index to the first element... + ind->cur_node = 0; + + // Return the first node... + if (ind->num_nodes) + return (ind->nodes[0]); + else + return (NULL); +} + + +// +// 'index_compare()' - Compare two nodes. +// + +static int // O - Result of comparison +index_compare(mxml_index_t *ind, // I - Index + mxml_node_t *first, // I - First node + mxml_node_t *second) // I - Second node +{ + int diff; // Difference + + + // Check the element name... + if ((diff = strcmp(first->value.element.name, second->value.element.name)) != 0) + return (diff); + + // Check the attribute value... + if (ind->attr) + { + if ((diff = strcmp(mxmlElementGetAttr(first, ind->attr), mxmlElementGetAttr(second, ind->attr))) != 0) + return (diff); + } + + // No difference, return 0... + return (0); +} + + +// +// 'index_find()' - Compare a node with index values. +// + +static int // O - Result of comparison +index_find(mxml_index_t *ind, // I - Index + const char *element, // I - Element name or `NULL` + const char *value, // I - Attribute value or `NULL` + mxml_node_t *node) // I - Node +{ + int diff; // Difference + + + // Check the element name... + if (element) + { + if ((diff = strcmp(element, node->value.element.name)) != 0) + return (diff); + } + + // Check the attribute value... + if (value) + { + if ((diff = strcmp(value, mxmlElementGetAttr(node, ind->attr))) != 0) + return (diff); + } + + // No difference, return 0... + return (0); +} + + +// +// 'index_sort()' - Sort the nodes in the index... +// +// This function implements the classic quicksort algorithm... +// + +static void +index_sort(mxml_index_t *ind, // I - Index to sort + int left, // I - Left node in partition + int right) // I - Right node in partition +{ + mxml_node_t *pivot, // Pivot node + *temp; // Swap node + int templ, // Temporary left node + tempr; // Temporary right node + + + // Loop until we have sorted all the way to the right... + do + { + // Sort the pivot in the current partition... + pivot = ind->nodes[left]; + + for (templ = left, tempr = right; templ < tempr;) + { + // Move left while left node <= pivot node... + while ((templ < right) && index_compare(ind, ind->nodes[templ], pivot) <= 0) + templ ++; + + // Move right while right node > pivot node... + while ((tempr > left) && index_compare(ind, ind->nodes[tempr], pivot) > 0) + tempr --; + + // Swap nodes if needed... + if (templ < tempr) + { + temp = ind->nodes[templ]; + ind->nodes[templ] = ind->nodes[tempr]; + ind->nodes[tempr] = temp; + } + } + + // When we get here, the right (tempr) node is the new position for the pivot node... + if (index_compare(ind, pivot, ind->nodes[tempr]) > 0) + { + ind->nodes[left] = ind->nodes[tempr]; + ind->nodes[tempr] = pivot; + } + + // Recursively sort the left partition as needed... + if (left < (tempr - 1)) + index_sort(ind, left, tempr - 1); + } + while (right > (left = tempr + 1)); +} diff --git a/src/main/mxml/mxml-node.c b/src/main/mxml/mxml-node.c new file mode 100644 index 0000000..a6978c9 --- /dev/null +++ b/src/main/mxml/mxml-node.c @@ -0,0 +1,937 @@ +// +// Node support code for Mini-XML, a small XML file parsing library. +// +// https://www.msweet.org/mxml +// +// Copyright © 2003-2024 by Michael R Sweet. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "mxml-private.h" + + +// +// Local functions... +// + +static void mxml_free(mxml_node_t *node); +static mxml_node_t *mxml_new(mxml_node_t *parent, mxml_type_t type); + + +// +// 'mxmlAdd()' - Add a node to a tree. +// +// This function adds the specified node `node` to the parent. If the `child` +// argument is not `NULL`, the new node is added before or after the specified +// child depending on the value of the `add` argument. If the `child` argument +// is `NULL`, the new node is placed at the beginning of the child list +// (`MXML_ADD_BEFORE`) or at the end of the child list (`MXML_ADD_AFTER`). +// + +void +mxmlAdd(mxml_node_t *parent, // I - Parent node + mxml_add_t add, // I - Where to add, `MXML_ADD_BEFORE` or `MXML_ADD_AFTER` + mxml_node_t *child, // I - Child node for where or `MXML_ADD_TO_PARENT` + mxml_node_t *node) // I - Node to add +{ + MXML_DEBUG("mxmlAdd(parent=%p, add=%d, child=%p, node=%p)\n", parent, add, child, node); + + // Range check input... + if (!parent || !node) + return; + + // Remove the node from any existing parent... + if (node->parent) + mxmlRemove(node); + + // Reset pointers... + node->parent = parent; + + switch (add) + { + case MXML_ADD_BEFORE : + if (!child || child == parent->child || child->parent != parent) + { + // Insert as first node under parent... + node->next = parent->child; + + if (parent->child) + parent->child->prev = node; + else + parent->last_child = node; + + parent->child = node; + } + else + { + // Insert node before this child... + node->next = child; + node->prev = child->prev; + + if (child->prev) + child->prev->next = node; + else + parent->child = node; + + child->prev = node; + } + break; + + case MXML_ADD_AFTER : + if (!child || child == parent->last_child || child->parent != parent) + { + // Insert as last node under parent... + node->parent = parent; + node->prev = parent->last_child; + + if (parent->last_child) + parent->last_child->next = node; + else + parent->child = node; + + parent->last_child = node; + } + else + { + // Insert node after this child... + node->prev = child; + node->next = child->next; + + if (child->next) + child->next->prev = node; + else + parent->last_child = node; + + child->next = node; + } + break; + } +} + + +// +// 'mxmlDelete()' - Delete a node and all of its children. +// +// This function deletes the node `node` and all of its children. If the +// specified node has a parent, this function first removes the node from its +// parent using the @link mxmlRemove@ function. +// + +void +mxmlDelete(mxml_node_t *node) // I - Node to delete +{ + mxml_node_t *current, // Current node + *next; // Next node + + + MXML_DEBUG("mxmlDelete(node=%p)\n", node); + + // Range check input... + if (!node) + return; + + // Remove the node from its parent, if any... + mxmlRemove(node); + + // Delete children... + for (current = node->child; current; current = next) + { + // Get the next node... + if ((next = current->child) != NULL) + { + // Free parent nodes after child nodes have been freed... + current->child = NULL; + continue; + } + + if ((next = current->next) == NULL) + { + // Next node is the parent, which we'll free as needed... + if ((next = current->parent) == node) + next = NULL; + } + + // Free child... + mxml_free(current); + } + + // Then free the memory used by the parent node... + mxml_free(node); +} + + +// +// 'mxmlGetRefCount()' - Get the current reference (use) count for a node. +// +// The initial reference count of new nodes is 1. Use the @link mxmlRetain@ +// and @link mxmlRelease@ functions to increment and decrement a node's +// reference count. +// + +size_t // O - Reference count +mxmlGetRefCount(mxml_node_t *node) // I - Node +{ + // Range check input... + if (!node) + return (0); + + // Return the reference count... + return (node->ref_count); +} + + +// +// 'mxmlNewCDATA()' - Create a new CDATA node. +// +// The new CDATA node is added to the end of the specified parent's child +// list. The constant `MXML_NO_PARENT` can be used to specify that the new +// CDATA node has no parent. The data string must be nul-terminated and +// is copied into the new node. CDATA nodes currently use the +// `MXML_TYPE_ELEMENT` type. +// + +mxml_node_t * // O - New node +mxmlNewCDATA(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT` + const char *data) // I - Data string +{ + mxml_node_t *node; // New node + + + MXML_DEBUG("mxmlNewCDATA(parent=%p, data=\"%s\")\n", parent, data ? data : "(null)"); + + // Range check input... + if (!data) + return (NULL); + + // Create the node and set the name value... + if ((node = mxml_new(parent, MXML_TYPE_CDATA)) != NULL) + { + if ((node->value.cdata = _mxml_strcopy(data)) == NULL) + { + mxmlDelete(node); + return (NULL); + } + } + + return (node); +} + + +// +// 'mxmlNewCDATAf()' - Create a new formatted CDATA node. +// +// The new CDATA node is added to the end of the specified parent's +// child list. The constant `MXML_NO_PARENT` can be used to specify that +// the new opaque string node has no parent. The format string must be +// nul-terminated and is formatted into the new node. +// + +mxml_node_t * // O - New node +mxmlNewCDATAf(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT` + const char *format, // I - Printf-style format string + ...) // I - Additional args as needed +{ + mxml_node_t *node; // New node + va_list ap; // Pointer to arguments + char buffer[16384]; // Format buffer + + + MXML_DEBUG("mxmlNewCDATAf(parent=%p, format=\"%s\", ...)\n", parent, format ? format : "(null)"); + + // Range check input... + if (!format) + return (NULL); + + // Create the node and set the text value... + if ((node = mxml_new(parent, MXML_TYPE_CDATA)) != NULL) + { + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + node->value.cdata = _mxml_strcopy(buffer); + } + + return (node); +} + + +// +// 'mxmlNewComment()' - Create a new comment node. +// +// The new comment node is added to the end of the specified parent's child +// list. The constant `MXML_NO_PARENT` can be used to specify that the new +// comment node has no parent. The comment string must be nul-terminated and +// is copied into the new node. +// + +mxml_node_t * // O - New node +mxmlNewComment(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT` + const char *comment) // I - Comment string +{ + mxml_node_t *node; // New node + + + MXML_DEBUG("mxmlNewComment(parent=%p, comment=\"%s\")\n", parent, comment ? comment : "(null)"); + + // Range check input... + if (!comment) + return (NULL); + + // Create the node and set the name value... + if ((node = mxml_new(parent, MXML_TYPE_COMMENT)) != NULL) + { + if ((node->value.comment = _mxml_strcopy(comment)) == NULL) + { + mxmlDelete(node); + return (NULL); + } + } + + return (node); +} + + +// +// 'mxmlNewCommentf()' - Create a new formatted comment string node. +// +// The new comment string node is added to the end of the specified parent's +// child list. The constant `MXML_NO_PARENT` can be used to specify that +// the new opaque string node has no parent. The format string must be +// nul-terminated and is formatted into the new node. +// + +mxml_node_t * // O - New node +mxmlNewCommentf(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT` + const char *format, // I - Printf-style format string + ...) // I - Additional args as needed +{ + mxml_node_t *node; // New node + va_list ap; // Pointer to arguments + char buffer[16384]; // Format buffer + + + MXML_DEBUG("mxmlNewCommentf(parent=%p, format=\"%s\", ...)\n", parent, format ? format : "(null)"); + + // Range check input... + if (!format) + return (NULL); + + // Create the node and set the text value... + if ((node = mxml_new(parent, MXML_TYPE_COMMENT)) != NULL) + { + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + node->value.comment = _mxml_strcopy(buffer); + } + + return (node); +} + + +// +// 'mxmlNewCustom()' - Create a new custom data node. +// +// The new custom node is added to the end of the specified parent's child +// list. The `free_cb` argument specifies a function to call to free the custom +// data when the node is deleted. +// + +mxml_node_t * // O - New node +mxmlNewCustom( + mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT` + void *data, // I - Pointer to data + mxml_custfree_cb_t free_cb, // I - Free callback function or `NULL` if none needed + void *free_cbdata) // I - Free callback data +{ + mxml_node_t *node; // New node + + + MXML_DEBUG("mxmlNewCustom(parent=%p, data=%p, free_cb=%p, free_cbdata=%p)\n", parent, data, free_cb, free_cbdata); + + // Create the node and set the value... + if ((node = mxml_new(parent, MXML_TYPE_CUSTOM)) != NULL) + { + node->value.custom.data = data; + node->value.custom.free_cb = free_cb; + node->value.custom.free_cbdata = free_cbdata; + } + + return (node); +} + + +// +// 'mxmlNewDeclaration()' - Create a new declaraction node. +// +// The new declaration node is added to the end of the specified parent's child +// list. The constant `MXML_NO_PARENT` can be used to specify that the new +// declaration node has no parent. The declaration string must be nul- +// terminated and is copied into the new node. +// + +mxml_node_t * // O - New node +mxmlNewDeclaration( + mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT` + const char *declaration) // I - Declaration string +{ + mxml_node_t *node; // New node + + + MXML_DEBUG("mxmlNewDeclaration(parent=%p, declaration=\"%s\")\n", parent, declaration ? declaration : "(null)"); + + // Range check input... + if (!declaration) + return (NULL); + + // Create the node and set the name value... + if ((node = mxml_new(parent, MXML_TYPE_DECLARATION)) != NULL) + { + if ((node->value.declaration = _mxml_strcopy(declaration)) == NULL) + { + mxmlDelete(node); + return (NULL); + } + } + + return (node); +} + + +// +// 'mxmlNewDeclarationf()' - Create a new formatted declaration node. +// +// The new declaration node is added to the end of the specified parent's +// child list. The constant `MXML_NO_PARENT` can be used to specify that +// the new opaque string node has no parent. The format string must be +// nul-terminated and is formatted into the new node. +// + +mxml_node_t * // O - New node +mxmlNewDeclarationf( + mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT` + const char *format, // I - Printf-style format string + ...) // I - Additional args as needed +{ + mxml_node_t *node; // New node + va_list ap; // Pointer to arguments + char buffer[16384]; // Format buffer + + + MXML_DEBUG("mxmlNewDeclarationf(parent=%p, format=\"%s\", ...)\n", parent, format ? format : "(null)"); + + // Range check input... + if (!format) + return (NULL); + + // Create the node and set the text value... + if ((node = mxml_new(parent, MXML_TYPE_DECLARATION)) != NULL) + { + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + node->value.declaration = _mxml_strcopy(buffer); + } + + return (node); +} + + +// +// 'mxmlNewDirective()' - Create a new processing instruction node. +// +// The new processing instruction node is added to the end of the specified +// parent's child list. The constant `MXML_NO_PARENT` can be used to specify +// that the new processing instruction node has no parent. The data string must +// be nul-terminated and is copied into the new node. +// + +mxml_node_t * // O - New node +mxmlNewDirective(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT` + const char *directive)// I - Directive string +{ + mxml_node_t *node; // New node + + + MXML_DEBUG("mxmlNewDirective(parent=%p, directive=\"%s\")\n", parent, directive ? directive : "(null)"); + + // Range check input... + if (!directive) + return (NULL); + + // Create the node and set the name value... + if ((node = mxml_new(parent, MXML_TYPE_DIRECTIVE)) != NULL) + { + if ((node->value.directive = _mxml_strcopy(directive)) == NULL) + { + mxmlDelete(node); + return (NULL); + } + } + + return (node); +} + + +// +// 'mxmlNewDirectivef()' - Create a new formatted processing instruction node. +// +// The new processing instruction node is added to the end of the specified parent's +// child list. The constant `MXML_NO_PARENT` can be used to specify that +// the new opaque string node has no parent. The format string must be +// nul-terminated and is formatted into the new node. +// + +mxml_node_t * // O - New node +mxmlNewDirectivef(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT` + const char *format, // I - Printf-style format string + ...) // I - Additional args as needed +{ + mxml_node_t *node; // New node + va_list ap; // Pointer to arguments + char buffer[16384]; // Format buffer + + + MXML_DEBUG("mxmlNewDirectivef(parent=%p, format=\"%s\", ...)\n", parent, format ? format : "(null)"); + + // Range check input... + if (!format) + return (NULL); + + // Create the node and set the text value... + if ((node = mxml_new(parent, MXML_TYPE_DIRECTIVE)) != NULL) + { + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + node->value.directive = _mxml_strcopy(buffer); + } + + return (node); +} + + +// +// 'mxmlNewElement()' - Create a new element node. +// +// The new element node is added to the end of the specified parent's child +// list. The constant `MXML_NO_PARENT` can be used to specify that the new +// element node has no parent. +// + +mxml_node_t * // O - New node +mxmlNewElement(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT` + const char *name) // I - Name of element +{ + mxml_node_t *node; // New node + + + MXML_DEBUG("mxmlNewElement(parent=%p, name=\"%s\")\n", parent, name ? name : "(null)"); + + // Range check input... + if (!name) + return (NULL); + + // Create the node and set the element name... + if ((node = mxml_new(parent, MXML_TYPE_ELEMENT)) != NULL) + node->value.element.name = _mxml_strcopy(name); + + return (node); +} + + +// +// 'mxmlNewInteger()' - Create a new integer node. +// +// The new integer node is added to the end of the specified parent's child +// list. The constant `MXML_NO_PARENT` can be used to specify that the new +// integer node has no parent. +// + +mxml_node_t * // O - New node +mxmlNewInteger(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT` + long integer) // I - Integer value +{ + mxml_node_t *node; // New node + + + MXML_DEBUG("mxmlNewInteger(parent=%p, integer=%ld)\n", parent, integer); + + // Create the node and set the element name... + if ((node = mxml_new(parent, MXML_TYPE_INTEGER)) != NULL) + node->value.integer = integer; + + return (node); +} + + +// +// 'mxmlNewOpaque()' - Create a new opaque string. +// +// The new opaque string node is added to the end of the specified parent's +// child list. The constant `MXML_NO_PARENT` can be used to specify that +// the new opaque string node has no parent. The opaque string must be nul- +// terminated and is copied into the new node. +// + +mxml_node_t * // O - New node +mxmlNewOpaque(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT` + const char *opaque) // I - Opaque string +{ + mxml_node_t *node; // New node + + + MXML_DEBUG("mxmlNewOpaque(parent=%p, opaque=\"%s\")\n", parent, opaque ? opaque : "(null)"); + + // Range check input... + if (!opaque) + return (NULL); + + // Create the node and set the element name... + if ((node = mxml_new(parent, MXML_TYPE_OPAQUE)) != NULL) + node->value.opaque = _mxml_strcopy(opaque); + + return (node); +} + + +// +// 'mxmlNewOpaquef()' - Create a new formatted opaque string node. +// +// The new opaque string node is added to the end of the specified parent's +// child list. The constant `MXML_NO_PARENT` can be used to specify that +// the new opaque string node has no parent. The format string must be +// nul-terminated and is formatted into the new node. +// + +mxml_node_t * // O - New node +mxmlNewOpaquef(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT` + const char *format, // I - Printf-style format string + ...) // I - Additional args as needed +{ + mxml_node_t *node; // New node + va_list ap; // Pointer to arguments + char buffer[16384]; // Format buffer + + + MXML_DEBUG("mxmlNewOpaquef(parent=%p, format=\"%s\", ...)\n", parent, format ? format : "(null)"); + + // Range check input... + if (!format) + return (NULL); + + // Create the node and set the text value... + if ((node = mxml_new(parent, MXML_TYPE_OPAQUE)) != NULL) + { + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + node->value.opaque = _mxml_strcopy(buffer); + } + + return (node); +} + + +// +// 'mxmlNewReal()' - Create a new real number node. +// +// The new real number node is added to the end of the specified parent's +// child list. The constant `MXML_NO_PARENT` can be used to specify that +// the new real number node has no parent. +// + +mxml_node_t * // O - New node +mxmlNewReal(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT` + double real) // I - Real number value +{ + mxml_node_t *node; // New node + + + MXML_DEBUG("mxmlNewReal(parent=%p, real=%g)\n", parent, real); + + // Create the node and set the element name... + if ((node = mxml_new(parent, MXML_TYPE_REAL)) != NULL) + node->value.real = real; + + return (node); +} + + +// +// 'mxmlNewText()' - Create a new text fragment node. +// +// The new text node is added to the end of the specified parent's child +// list. The constant `MXML_NO_PARENT` can be used to specify that the new +// text node has no parent. The whitespace parameter is used to specify +// whether leading whitespace is present before the node. The text +// string must be nul-terminated and is copied into the new node. +// + +mxml_node_t * // O - New node +mxmlNewText(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT` + bool whitespace, // I - `true` = leading whitespace, `false` = no whitespace + const char *string) // I - String +{ + mxml_node_t *node; // New node + + + MXML_DEBUG("mxmlNewText(parent=%p, whitespace=%s, string=\"%s\")\n", parent, whitespace ? "true" : "false", string ? string : "(null)"); + + // Range check input... + if (!string) + return (NULL); + + // Create the node and set the text value... + if ((node = mxml_new(parent, MXML_TYPE_TEXT)) != NULL) + { + node->value.text.whitespace = whitespace; + node->value.text.string = _mxml_strcopy(string); + } + + return (node); +} + + +// +// 'mxmlNewTextf()' - Create a new formatted text fragment node. +// +// The new text node is added to the end of the specified parent's child +// list. The constant `MXML_NO_PARENT` can be used to specify that the new +// text node has no parent. The whitespace parameter is used to specify +// whether leading whitespace is present before the node. The format +// string must be nul-terminated and is formatted into the new node. +// + +mxml_node_t * // O - New node +mxmlNewTextf(mxml_node_t *parent, // I - Parent node or `MXML_NO_PARENT` + bool whitespace, // I - `true` = leading whitespace, `false` = no whitespace + const char *format, // I - Printf-style format string + ...) // I - Additional args as needed +{ + mxml_node_t *node; // New node + va_list ap; // Pointer to arguments + char buffer[16384]; // Format buffer + + + MXML_DEBUG("mxmlNewTextf(parent=%p, whitespace=%s, format=\"%s\", ...)\n", parent, whitespace ? "true" : "false", format ? format : "(null)"); + + // Range check input... + if (!format) + return (NULL); + + // Create the node and set the text value... + if ((node = mxml_new(parent, MXML_TYPE_TEXT)) != NULL) + { + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + node->value.text.whitespace = whitespace; + node->value.text.string = _mxml_strcopy(buffer); + } + + return (node); +} + + +// +// 'mxmlRemove()' - Remove a node from its parent. +// +// This function does not free memory used by the node - use @link mxmlDelete@ +// for that. This function does nothing if the node has no parent. +// + +void +mxmlRemove(mxml_node_t *node) // I - Node to remove +{ + MXML_DEBUG("mxmlRemove(node=%p)\n", node); + + // Range check input... + if (!node || !node->parent) + return; + + // Remove from parent... + if (node->prev) + node->prev->next = node->next; + else + node->parent->child = node->next; + + if (node->next) + node->next->prev = node->prev; + else + node->parent->last_child = node->prev; + + node->parent = NULL; + node->prev = NULL; + node->next = NULL; +} + + +// +// 'mxmlNewXML()' - Create a new XML document tree. +// +// The "version" argument specifies the version number to put in the +// ?xml directive node. If `NULL`, version "1.0" is assumed. +// + +mxml_node_t * // O - New ?xml node +mxmlNewXML(const char *version) // I - Version number to use +{ + char directive[1024]; // Directive text + + + snprintf(directive, sizeof(directive), "xml version=\"%s\" encoding=\"utf-8\"", version ? version : "1.0"); + + return (mxmlNewDirective(NULL, directive)); +} + + +// +// 'mxmlRelease()' - Release a node. +// +// When the reference count reaches zero, the node (and any children) +// is deleted via @link mxmlDelete@. +// + +int // O - New reference count +mxmlRelease(mxml_node_t *node) // I - Node +{ + if (node) + { + if ((-- node->ref_count) <= 0) + { + mxmlDelete(node); + return (0); + } + else + { + return (node->ref_count); + } + } + else + { + return (-1); + } +} + + +// +// 'mxmlRetain()' - Retain a node. +// + +int // O - New reference count +mxmlRetain(mxml_node_t *node) // I - Node +{ + if (node) + return (++ node->ref_count); + else + return (-1); +} + + +// +// 'mxml_free()' - Free the memory used by a node. +// +// Note: Does not free child nodes, does not remove from parent. +// + +static void +mxml_free(mxml_node_t *node) // I - Node +{ + size_t i; // Looping var + + + switch (node->type) + { + case MXML_TYPE_CDATA : + _mxml_strfree(node->value.cdata); + break; + case MXML_TYPE_COMMENT : + _mxml_strfree(node->value.comment); + break; + case MXML_TYPE_DECLARATION : + _mxml_strfree(node->value.declaration); + break; + case MXML_TYPE_DIRECTIVE : + _mxml_strfree(node->value.directive); + break; + case MXML_TYPE_ELEMENT : + _mxml_strfree(node->value.element.name); + + if (node->value.element.num_attrs) + { + for (i = 0; i < node->value.element.num_attrs; i ++) + { + _mxml_strfree(node->value.element.attrs[i].name); + _mxml_strfree(node->value.element.attrs[i].value); + } + + free(node->value.element.attrs); + } + break; + case MXML_TYPE_INTEGER : + // Nothing to do + break; + case MXML_TYPE_OPAQUE : + _mxml_strfree(node->value.opaque); + break; + case MXML_TYPE_REAL : + // Nothing to do + break; + case MXML_TYPE_TEXT : + _mxml_strfree(node->value.text.string); + break; + case MXML_TYPE_CUSTOM : + if (node->value.custom.data && node->value.custom.free_cb) + (node->value.custom.free_cb)(node->value.custom.free_cbdata, node->value.custom.data); + break; + default : + break; + } + + // Free this node... + free(node); +} + + +// +// 'mxml_new()' - Create a new node. +// + +static mxml_node_t * // O - New node +mxml_new(mxml_node_t *parent, // I - Parent node + mxml_type_t type) // I - Node type +{ + mxml_node_t *node; // New node + + + MXML_DEBUG("mxml_new(parent=%p, type=%d)\n", parent, type); + + // Allocate memory for the node... + if ((node = calloc(1, sizeof(mxml_node_t))) == NULL) + { + MXML_DEBUG("mxml_new: Returning NULL\n"); + return (NULL); + } + + MXML_DEBUG("mxml_new: Returning %p\n", node); + + // Set the node type... + node->type = type; + node->ref_count = 1; + + // Add to the parent if present... + if (parent) + mxmlAdd(parent, MXML_ADD_AFTER, /*child*/NULL, node); + + // Return the new node... + return (node); +} diff --git a/src/main/mxml/mxml-options.c b/src/main/mxml/mxml-options.c new file mode 100644 index 0000000..1f521c2 --- /dev/null +++ b/src/main/mxml/mxml-options.c @@ -0,0 +1,545 @@ +// +// Options functions for Mini-XML, a small XML file parsing library. +// +// https://www.msweet.org/mxml +// +// Copyright © 2003-2024 by Michael R Sweet. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "mxml-private.h" + + +// +// 'mxmlOptionsDelete()' - Free load/save options. +// + +void +mxmlOptionsDelete( + mxml_options_t *options) // I - Options +{ + free(options); +} + + +// +// 'mxmlOptionsNew()' - Allocate load/save options. +// +// This function creates a new set of load/save options to use with the +// @link mxmlLoadFd@, @link mxmlLoadFile@, @link mxmlLoadFilename@, +// @link mxmlLoadIO@, @link mxmlLoadString@, @link mxmlSaveAllocString@, +// @link mxmlSaveFd@, @link mxmlSaveFile@, @link mxmlSaveFilename@, +// @link mxmlSaveIO@, and @link mxmlSaveString@ functions. Options can be +// reused for multiple calls to these functions and should be freed using the +// @link mxmlOptionsDelete@ function. +// +// The default load/save options load values using the constant type +// `MXML_TYPE_TEXT` and save XML data with a wrap margin of 72 columns. +// The various `mxmlOptionsSet` functions are used to change the defaults, +// for example: +// +// ```c +// mxml_options_t *options = mxmlOptionsNew(); +// +// /* Load values as opaque strings */ +// mxmlOptionsSetTypeValue(options, MXML_TYPE_OPAQUE); +// ``` +// +// Note: The most common programming error when using the Mini-XML library is +// to load an XML file using the `MXML_TYPE_TEXT` node type, which returns +// inline text as a series of whitespace-delimited words, instead of using the +// `MXML_TYPE_OPAQUE` node type which returns the inline text as a single string +// (including whitespace). +// + +mxml_options_t * // O - Options +mxmlOptionsNew(void) +{ + mxml_options_t *options; // Options + + + if ((options = (mxml_options_t *)calloc(1, sizeof(mxml_options_t))) != NULL) + { + // Set default values... + options->type_value = MXML_TYPE_TEXT; + options->wrap = 72; + + if ((options->loc = localeconv()) != NULL) + { + if (!options->loc->decimal_point || !strcmp(options->loc->decimal_point, ".")) + options->loc = NULL; + else + options->loc_declen = strlen(options->loc->decimal_point); + } + } + + return (options); +} + + +// +// 'mxmlOptionsSetCustomCallbacks()' - 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; +// +// void +// my_custom_free_cb(void *cbdata, void *data) +// { +// free(data); +// } +// +// 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, my_custom_free, /*cbdata*/NULL); +// +// /* 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 +mxmlOptionsSetCustomCallbacks( + mxml_options_t *options, // I - Options + mxml_custload_cb_t load_cb, // I - Custom load callback function + mxml_custsave_cb_t save_cb, // I - Custom save callback function + void *cbdata) // I - Custom callback data +{ + if (options) + { + options->custload_cb = load_cb; + options->custsave_cb = save_cb; + options->cust_cbdata = cbdata; + } +} + + +// +// 'mxmlOptionsSetEntityCallback()' - Set the entity lookup callback to use when loading XML data. +// +// This function sets the callback that is used to lookup named XML character +// entities when loading XML data. The callback function `cb` accepts the +// callback data pointer `cbdata` and the entity name. The function returns a +// Unicode character value or `-1` if the entity is not known. For example, the +// following entity callback supports the "euro" entity: +// +// ```c +// int my_entity_cb(void *cbdata, const char *name) +// { +// if (!strcmp(name, "euro")) +// return (0x20ac); +// else +// return (-1); +// } +// ``` +// +// Mini-XML automatically supports the "amp", "gt", "lt", and "quot" character +// entities which are required by the base XML specification. +// + +void +mxmlOptionsSetEntityCallback( + mxml_options_t *options, // I - Options + mxml_entity_cb_t cb, // I - Entity callback function + void *cbdata) // I - Entity callback data +{ + if (options) + { + options->entity_cb = cb; + options->entity_cbdata = cbdata; + } +} + + +// +// 'mxmlOptionsSetErrorCallback()' - 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 +mxmlOptionsSetErrorCallback( + mxml_options_t *options, // I - Options + mxml_error_cb_t cb, // I - Error callback function + void *cbdata) // I - Error callback data +{ + if (options) + { + options->error_cb = cb; + options->error_cbdata = cbdata; + } +} + + +// +// 'mxmlOptionsSetSAXCallback()' - Set the SAX callback to use when reading XML data. +// +// This function sets a SAX callback to use when reading XML data. The SAX +// callback function `cb` and associated callback data `cbdata` are used to +// enable the Simple API for XML streaming mode. The callback is called as the +// XML node tree is parsed and receives the `cbdata` pointer, the `mxml_node_t` +// pointer, and an event code. The function returns `true` to continue +// processing or `false` to stop: +// +// ```c +// bool +// sax_cb(void *cbdata, mxml_node_t *node, +// mxml_sax_event_t event) +// { +// ... do something ... +// +// /* Continue processing... */ +// return (true); +// } +// ``` +// +// The event will be one of the following: +// +// - `MXML_SAX_EVENT_CDATA`: CDATA was just read. +// - `MXML_SAX_EVENT_COMMENT`: A comment was just read. +// - `MXML_SAX_EVENT_DATA`: Data (integer, opaque, real, or text) was just read. +// - `MXML_SAX_EVENT_DECLARATION`: A declaration was just read. +// - `MXML_SAX_EVENT_DIRECTIVE`: A processing directive/instruction was just read. +// - `MXML_SAX_EVENT_ELEMENT_CLOSE` - A close element was just read \(``) +// - `MXML_SAX_EVENT_ELEMENT_OPEN` - An open element was just read \(``) +// +// Elements are *released* after the close element is processed. All other nodes +// are released after they are processed. The SAX callback can *retain* the node +// using the [mxmlRetain](@@) function. +// + +void +mxmlOptionsSetSAXCallback( + mxml_options_t *options, // I - Options + mxml_sax_cb_t cb, // I - SAX callback function + void *cbdata) // I - SAX callback data +{ + if (options) + { + options->sax_cb = cb; + options->sax_cbdata = cbdata; + } +} + + +// +// 'mxmlOptionsSetTypeCallback()' - Set the type callback for child/value nodes. +// +// The load callback function `cb` is called to obtain the node type child/value +// nodes and receives the `cbdata` pointer and the `mxml_node_t` pointer, for +// example: +// +// ```c +// mxml_type_t +// my_type_cb(void *cbdata, mxml_node_t *node) +// { +// const char *type; +// +// /* +// * You can lookup attributes and/or use the element name, +// * hierarchy, etc... +// */ +// +// type = mxmlElementGetAttr(node, "type"); +// if (type == NULL) +// type = mxmlGetElement(node); +// if (type == NULL) +// type = "text"; +// +// if (!strcmp(type, "integer")) +// return (MXML_TYPE_INTEGER); +// else if (!strcmp(type, "opaque")) +// return (MXML_TYPE_OPAQUE); +// else if (!strcmp(type, "real")) +// return (MXML_TYPE_REAL); +// else +// return (MXML_TYPE_TEXT); +// } +// ``` +// + +void +mxmlOptionsSetTypeCallback( + mxml_options_t *options, // I - Options + mxml_type_cb_t cb, // I - Type callback function + void *cbdata) // I - Type callback data +{ + if (options) + { + options->type_cb = cb; + options->type_cbdata = cbdata; + } +} + + +// +// 'mxmlOptionsSetTypeValue()' - Set the type to use for all child/value nodes. +// +// This functions sets a constant node type to use for all child/value nodes. +// + +void +mxmlOptionsSetTypeValue( + mxml_options_t *options, // I - Options + mxml_type_t type) // I - Value node type +{ + if (options) + { + options->type_cb = NULL; + options->type_value = type; + } +} + + +// +// 'mxmlOptionsSetWhitespaceCallback()' - Set the whitespace callback. +// +// This function sets the whitespace callback that is used when saving XML data. +// The callback function `cb` specifies a function that returns a whitespace +// string or `NULL` before and after each element. The function receives the +// callback data pointer `cbdata`, the `mxml_node_t` pointer, and a "when" +// value indicating where the whitespace is being added, for example: +// +// ```c +// const char *my_whitespace_cb(void *cbdata, mxml_node_t *node, mxml_ws_t when) +// { +// if (when == MXML_WS_BEFORE_OPEN || when == MXML_WS_AFTER_CLOSE) +// return ("\n"); +// else +// return (NULL); +// } +// ``` +// + +void +mxmlOptionsSetWhitespaceCallback( + mxml_options_t *options, // I - Options + mxml_ws_cb_t cb, // I - Whitespace callback function + void *cbdata) // I - Whitespace callback data +{ + if (options) + { + options->ws_cb = cb; + options->ws_cbdata = cbdata; + } +} + + +// +// 'mxmlOptionsSetWrapMargin()' - Set the wrap margin when saving XML data. +// +// This function sets the wrap margin used when saving XML data. Wrapping is +// disabled when `column` is `0`. +// + +void +mxmlOptionsSetWrapMargin( + mxml_options_t *options, // I - Options + int column) // I - Wrap column +{ + if (options) + options->wrap = column; +} + + +// +// '_mxml_entity_string()' - Get the entity that corresponds to the character, if any. +// + +const char * // O - Entity or `NULL` for none +_mxml_entity_string(int ch) // I - Character +{ + switch (ch) + { + case '&' : + return ("&"); + + case '<' : + return ("<"); + + case '>' : + return (">"); + + case '\"' : + return ("""); + + default : + return (NULL); + } +} + + +// +// '_mxml_entity_value()' - Get the character corresponding to a named entity. +// +// The entity name can also be a numeric constant. `-1` is returned if the +// name is not known. +// + +int // O - Unicode character +_mxml_entity_value( + mxml_options_t *options, // I - Options + const char *name) // I - Entity name +{ + int ch = -1; // Unicode character + + + if (!name) + { + // No name... + return (-1); + } + else if (*name == '#') + { + // Numeric entity... + if (name[1] == 'x') + ch = (int)strtol(name + 2, NULL, 16); + else + ch = (int)strtol(name + 1, NULL, 10); + } + else if (!strcmp(name, "amp")) + { + // Ampersand + ch = '&'; + } + else if (!strcmp(name, "gt")) + { + // Greater than + ch = '>'; + } + else if (!strcmp(name, "lt")) + { + // Less than + ch = '<'; + } + else if (!strcmp(name, "quot")) + { + // Double quote + ch = '\"'; + } + else if (options && options->entity_cb) + { + // Use callback + ch = (options->entity_cb)(options->entity_cbdata, name); + } + + return (ch); +} + + +// +// '_mxml_error()' - Display an error message. +// + +void +_mxml_error(mxml_options_t *options, // I - Load/save options + const char *format, // I - Printf-style format string + ...) // I - Additional arguments as needed +{ + va_list ap; // Pointer to arguments + char s[1024]; // Message string + + + // Range check input... + if (!format) + return; + + // Format the error message string... + va_start(ap, format); + vsnprintf(s, sizeof(s), format, ap); + va_end(ap); + + // And then display the error message... + if (options->error_cb) + (options->error_cb)(options->error_cbdata, s); + else + fprintf(stderr, "%s\n", s); +} diff --git a/src/main/mxml/mxml-private.c b/src/main/mxml/mxml-private.c new file mode 100644 index 0000000..2c26f10 --- /dev/null +++ b/src/main/mxml/mxml-private.c @@ -0,0 +1,280 @@ +// +// Private functions for Mini-XML, a small XML file parsing library. +// +// https://www.msweet.org/mxml +// +// Copyright © 2003-2024 by Michael R Sweet. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "mxml-private.h" + + +// +// Some crazy people think that unloading a shared object is a good or safe +// thing to do. Unfortunately, most objects are simply *not* safe to unload +// and bad things *will* happen. +// +// The following mess of conditional code allows us to provide a destructor +// function in Mini-XML for our thread-global storage so that it can possibly +// be unloaded safely, although since there is no standard way to do so I +// can't even provide any guarantees that you can do it safely on all platforms. +// +// This code currently supports AIX, HP-UX, Linux, macOS, Solaris, and +// Windows. It might work on the BSDs and IRIX, but I haven't tested that. +// + +#if defined(__sun) || defined(_AIX) +# pragma fini(_mxml_fini) +# define _MXML_FINI _mxml_fini +#elif defined(__hpux) +# pragma FINI _mxml_fini +# define _MXML_FINI _mxml_fini +#elif defined(__GNUC__) // Linux and macOS +# define _MXML_FINI __attribute((destructor)) _mxml_fini +#else +# define _MXML_FINI _fini +#endif // __sun + + +// +// '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_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 + +static int _mxml_initialized = 0; + // Have we been initialized? +static pthread_key_t _mxml_key; // Thread local storage key +static pthread_once_t _mxml_key_once = PTHREAD_ONCE_INIT; + // One-time initialization object +static void _mxml_init(void); +static void _mxml_destructor(void *g); + + +// +// '_mxml_destructor()' - Free memory used for globals... +// + +static void +_mxml_destructor(void *g) // I - Global data +{ + free(g); +} + + +// +// '_mxml_fini()' - Clean up when unloaded. +// + +static void +_MXML_FINI(void) +{ + if (_mxml_initialized) + pthread_key_delete(_mxml_key); +} + + +// +// '_mxml_global()' - Get global data. +// + +_mxml_global_t * // O - Global data +_mxml_global(void) +{ + _mxml_global_t *global; // Global data + + + pthread_once(&_mxml_key_once, _mxml_init); + + if ((global = (_mxml_global_t *)pthread_getspecific(_mxml_key)) == NULL) + { + global = (_mxml_global_t *)calloc(1, sizeof(_mxml_global_t)); + pthread_setspecific(_mxml_key, global); + } + + return (global); +} + + +// +// '_mxml_init()' - Initialize global data... +// + +static void +_mxml_init(void) +{ + _mxml_initialized = 1; + pthread_key_create(&_mxml_key, _mxml_destructor); +} + + +#elif defined(_WIN32) && defined(MXML1_EXPORTS) // WIN32 threading +# include + +static DWORD _mxml_tls_index; // Index for global storage + + +// +// 'DllMain()' - Main entry for library. +// + +BOOL WINAPI // O - Success/failure +DllMain(HINSTANCE hinst, // I - DLL module handle + DWORD reason, // I - Reason + LPVOID reserved) // I - Unused +{ + _mxml_global_t *global; // Global data + + + (void)hinst; + (void)reserved; + + switch (reason) + { + case DLL_PROCESS_ATTACH : // Called on library initialization + if ((_mxml_tls_index = TlsAlloc()) == TLS_OUT_OF_INDEXES) + return (FALSE); + break; + + case DLL_THREAD_DETACH : // Called when a thread terminates + if ((global = (_mxml_global_t *)TlsGetValue(_mxml_tls_index)) != NULL) + free(global); + break; + + case DLL_PROCESS_DETACH : // Called when library is unloaded + if ((global = (_mxml_global_t *)TlsGetValue(_mxml_tls_index)) != NULL) + free(global); + + TlsFree(_mxml_tls_index); + break; + + default: + break; + } + + return (TRUE); +} + + +// +// '_mxml_global()' - Get global data. +// + +_mxml_global_t * // O - Global data +_mxml_global(void) +{ + _mxml_global_t *global; // Global data + + + if ((global = (_mxml_global_t *)TlsGetValue(_mxml_tls_index)) == NULL) + { + global = (_mxml_global_t *)calloc(1, sizeof(_mxml_global_t)); + + TlsSetValue(_mxml_tls_index, (LPVOID)global); + } + + return (global); +} + + +#else // No threading +// +// '_mxml_global()' - Get global data. +// + +_mxml_global_t * // O - Global data +_mxml_global(void) +{ + static _mxml_global_t global = // Global data + { + NULL, // strcopy_cb + NULL, // strfree_cb + NULL, // str_cbdata + }; + + + return (&global); +} +#endif // HAVE_PTHREAD_H diff --git a/src/main/mxml/mxml-private.h b/src/main/mxml/mxml-private.h new file mode 100644 index 0000000..b2346e1 --- /dev/null +++ b/src/main/mxml/mxml-private.h @@ -0,0 +1,140 @@ +// +// Private definitions for Mini-XML, a small XML file parsing library. +// +// https://www.msweet.org/mxml +// +// Copyright © 2003-2024 by Michael R Sweet. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#ifndef MXML_PRIVATE_H +# define MXML_PRIVATE_H +# include "config.h" +# include "mxml.h" +# include + + +// +// Private macros... +// + +# ifdef DEBUG +# define MXML_DEBUG(...) fprintf(stderr, __VA_ARGS__) +# else +# define MXML_DEBUG(...) +# endif // DEBUG +# ifndef MXML_ALLOC_SIZE +# define MXML_ALLOC_SIZE 16 // Allocation increment +# endif // !MXML_ALLOC_SIZE +# define MXML_TAB 8 // Tabs every N columns + + +// +// Private structures... +// + +typedef struct _mxml_attr_s // An XML element attribute value. +{ + char *name; // Attribute name + char *value; // Attribute value +} _mxml_attr_t; + +typedef struct _mxml_element_s // An XML element value. +{ + char *name; // Name of element + size_t num_attrs; // Number of attributes + _mxml_attr_t *attrs; // Attributes +} _mxml_element_t; + +typedef struct _mxml_text_s // An XML text value. +{ + bool whitespace; // Leading whitespace? + char *string; // Fragment string +} _mxml_text_t; + +typedef struct _mxml_custom_s // An XML custom value. +{ + void *data; // Pointer to (allocated) custom data + mxml_custfree_cb_t free_cb; // Free callback function + void *free_cbdata; // Free callback data +} _mxml_custom_t; + +typedef union _mxml_value_u // An XML node value. +{ + char *cdata; // CDATA string + char *comment; // Common string + char *declaration; // Declaration string + char *directive; // Processing instruction string + _mxml_element_t element; // Element + long integer; // Integer number + char *opaque; // Opaque string + double real; // Real number + _mxml_text_t text; // Text fragment + _mxml_custom_t custom; // Custom data +} _mxml_value_t; + +struct _mxml_node_s // An XML node. +{ + mxml_type_t type; // Node type + struct _mxml_node_s *next; // Next node under same parent + struct _mxml_node_s *prev; // Previous node under same parent + struct _mxml_node_s *parent; // Parent node + struct _mxml_node_s *child; // First child node + struct _mxml_node_s *last_child; // Last child node + _mxml_value_t value; // Node value + size_t ref_count; // Use count + void *user_data; // User data +}; + +typedef struct _mxml_global_s // Global, per-thread data +{ + 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 +} _mxml_global_t; + +struct _mxml_index_s // An XML node index. +{ + char *attr; // Attribute used for indexing or NULL + size_t num_nodes; // Number of nodes in index + size_t alloc_nodes; // Allocated nodes in index + size_t cur_node; // Current node + mxml_node_t **nodes; // Node array +}; + +struct _mxml_options_s // XML options +{ + struct lconv *loc; // Locale data + size_t loc_declen; // Length of decimal point string + mxml_custload_cb_t custload_cb; // Custom load callback function + mxml_custsave_cb_t custsave_cb; // Custom save callback function + void *cust_cbdata; // Custom callback data + mxml_entity_cb_t entity_cb; // Entity callback function + void *entity_cbdata; // Entity callback data + mxml_error_cb_t error_cb; // Error callback function + void *error_cbdata; // Error callback data + mxml_sax_cb_t sax_cb; // SAX callback function + void *sax_cbdata; // SAX callback data + mxml_type_cb_t type_cb; // Type callback function + void *type_cbdata; // Type callback data + mxml_type_t type_value; // Fixed type value (if no type callback) + int wrap; // Wrap margin + mxml_ws_cb_t ws_cb; // Whitespace callback function + void *ws_cbdata; // Whitespace callback data +}; + + +// +// Private functions... +// + +extern _mxml_global_t *_mxml_global(void); +extern const char *_mxml_entity_string(int ch); +extern int _mxml_entity_value(mxml_options_t *options, const char *name); +extern void _mxml_error(mxml_options_t *options, const char *format, ...) MXML_FORMAT(2,3); +extern char *_mxml_strcopy(const char *s); +extern void _mxml_strfree(char *s); + +#endif // !MXML_PRIVATE_H diff --git a/src/main/mxml/mxml-search.c b/src/main/mxml/mxml-search.c new file mode 100644 index 0000000..d9a4768 --- /dev/null +++ b/src/main/mxml/mxml-search.c @@ -0,0 +1,237 @@ +// +// Search/navigation functions for Mini-XML, a small XML file parsing library. +// +// https://www.msweet.org/mxml +// +// Copyright © 2003-2024 by Michael R Sweet. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "mxml-private.h" + + +// +// 'mxmlFindElement()' - Find the named element. +// +// This function finds the named element `element` in XML tree `top` starting at +// node `node`. The search is constrained by element name `element`, attribute +// name `attr`, and attribute value `value` - `NULL` names or values are treated +// as wildcards, so different kinds of searches can be implemented by looking +// for all elements of a given name or all elements with a specific attribute. +// +// The `descend` argument determines whether the search descends into child +// nodes; normally you will use `MXML_DESCEND_FIRST` for the initial search and +// `MXML_DESCEND_NONE` to find additional direct descendents of the node. +// + +mxml_node_t * // O - Element node or `NULL` +mxmlFindElement(mxml_node_t *node, // I - Current node + mxml_node_t *top, // I - Top node + const char *element,// I - Element name or `NULL` for any + const char *attr, // I - Attribute name, or `NULL` for none + const char *value, // I - Attribute value, or `NULL` for any + mxml_descend_t descend) // I - Descend into tree - `MXML_DESCEND_ALL`, `MXML_DESCEND_NONE`, or `MXML_DESCEND_FIRST` +{ + const char *temp; // Current attribute value + + // Range check input... + if (!node || !top || (!attr && value)) + return (NULL); + + // Start with the next node... + node = mxmlWalkNext(node, top, descend); + + // Loop until we find a matching element... + while (node != NULL) + { + // See if this node matches... + if (node->type == MXML_TYPE_ELEMENT && node->value.element.name && (!element || !strcmp(node->value.element.name, element))) + { + // See if we need to check for an attribute... + if (!attr) + return (node); // No attribute search, return it... + + // Check for the attribute... + if ((temp = mxmlElementGetAttr(node, attr)) != NULL) + { + // OK, we have the attribute, does it match? + if (!value || !strcmp(value, temp)) + return (node); // Yes, return it... + } + } + + // No match, move on to the next node... + if (descend == MXML_DESCEND_ALL) + node = mxmlWalkNext(node, top, MXML_DESCEND_ALL); + else + node = node->next; + } + + return (NULL); +} + + +// +// 'mxmlFindPath()' - Find a node with the given path. +// +// This function finds a node in XML tree `top` using a slash-separated list of +// element names in `path`. The name "*" is considered a wildcard for one or +// more levels of elements, for example, "foo/one/two", "bar/two/one", "*\/one", +// and so forth. +// +// The first child node of the found node is returned if the given node has +// children and the first child is a value node. +// + +mxml_node_t * // O - Found node or `NULL` +mxmlFindPath(mxml_node_t *top, // I - Top node + const char *path) // I - Path to element +{ + mxml_node_t *node; // Current node + char element[256]; // Current element name + const char *pathsep; // Separator in path + mxml_descend_t descend; // mxmlFindElement option + + + // Range check input... + if (!top || !path || !*path) + return (NULL); + + // Search each element in the path... + node = top; + while (*path) + { + // Handle wildcards... + if (!strncmp(path, "*/", 2)) + { + path += 2; + descend = MXML_DESCEND_ALL; + } + else + { + descend = MXML_DESCEND_FIRST; + } + + // Get the next element in the path... + if ((pathsep = strchr(path, '/')) == NULL) + pathsep = path + strlen(path); + + if (pathsep == path || (size_t)(pathsep - path) >= sizeof(element)) + return (NULL); + + memcpy(element, path, pathsep - path); + element[pathsep - path] = '\0'; + + if (*pathsep) + path = pathsep + 1; + else + path = pathsep; + + // Search for the element... + if ((node = mxmlFindElement(node, node, element, NULL, NULL, descend)) == NULL) + return (NULL); + } + + // If we get this far, return the node or its first child... + // if (node->child && node->child->type != MXML_TYPE_ELEMENT) + // return (node->child); + // else + return (node); +} + + +// +// 'mxmlWalkNext()' - Walk to the next logical node in the tree. +// +// This function walks to the next logical node in the tree. The `descend` +// argument controls whether the first child is considered to be the next node. +// The `top` argument constrains the walk to that node's children. +// + +mxml_node_t * // O - Next node or `NULL` +mxmlWalkNext(mxml_node_t *node, // I - Current node + mxml_node_t *top, // I - Top node + mxml_descend_t descend) // I - Descend into tree - `MXML_DESCEND_ALL`, `MXML_DESCEND_NONE`, or `MXML_DESCEND_FIRST` +{ + if (!node) + { + return (NULL); + } + else if (node->child && descend != MXML_DESCEND_NONE) + { + return (node->child); + } + else if (node == top) + { + return (NULL); + } + else if (node->next) + { + return (node->next); + } + else if (node->parent && node->parent != top) + { + node = node->parent; + + while (!node->next) + { + if (node->parent == top || !node->parent) + return (NULL); + else + node = node->parent; + } + + return (node->next); + } + else + { + return (NULL); + } +} + + +// +// 'mxmlWalkPrev()' - Walk to the previous logical node in the tree. +// +// This function walks to the previous logical node in the tree. The `descend` +// argument controls whether the first child is considered to be the next node. +// The `top` argument constrains the walk to that node's children. +// + +mxml_node_t * // O - Previous node or `NULL` +mxmlWalkPrev(mxml_node_t *node, // I - Current node + mxml_node_t *top, // I - Top node + mxml_descend_t descend) // I - Descend into tree - `MXML_DESCEND_ALL`, `MXML_DESCEND_NONE`, or `MXML_DESCEND_FIRST` +{ + if (!node || node == top) + { + return (NULL); + } + else if (node->prev) + { + if (node->prev->last_child && descend != MXML_DESCEND_NONE) + { + // Find the last child under the previous node... + node = node->prev->last_child; + + while (node->last_child) + node = node->last_child; + + return (node); + } + else + { + return (node->prev); + } + } + else if (node->parent != top) + { + return (node->parent); + } + else + { + return (NULL); + } +} diff --git a/src/main/mxml/mxml-set.c b/src/main/mxml/mxml-set.c new file mode 100644 index 0000000..79b6218 --- /dev/null +++ b/src/main/mxml/mxml-set.c @@ -0,0 +1,630 @@ +// +// Node set functions for Mini-XML, a small XML file parsing library. +// +// https://www.msweet.org/mxml +// +// Copyright © 2003-2024 by Michael R Sweet. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "mxml-private.h" + + +// +// 'mxmlSetCDATA()' - Set the data for a CDATA node. +// +// This function sets the value string for a CDATA node. The node is not +// changed if it (or its first child) is not a CDATA node. +// + +bool // O - `true` on success, `false` on failure +mxmlSetCDATA(mxml_node_t *node, // I - Node to set + const char *data) // I - New data string +{ + char *s; // New element name + + + // Range check input... + if (node && node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_CDATA) + node = node->child; + + if (!node || node->type != MXML_TYPE_CDATA) + return (false); + else if (!data) + return (false); + + if (data == node->value.cdata) + { + // Don't change the value... + return (true); + } + + // Allocate the new value, free any old element value, and set the new value... + if ((s = _mxml_strcopy(data)) == NULL) + return (false); + + _mxml_strfree(node->value.cdata); + node->value.cdata = s; + + return (true); +} + + +// +// 'mxmlSetCDATAf()' - Set the data for a CDATA to a formatted string. +// +// This function sets the formatted string value of a CDATA node. The node is +// not changed if it (or its first child) is not a CDATA node. +// + +bool // O - `true` on success, `false` on failure +mxmlSetCDATAf(mxml_node_t *node, // I - Node + const char *format, // I - `printf`-style format string + ...) // I - Additional arguments as needed +{ + va_list ap; // Pointer to arguments + char buffer[16384]; // Format buffer + char *s; // Temporary string + + + // Range check input... + if (node && node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_CDATA) + node = node->child; + + if (!node || node->type != MXML_TYPE_CDATA) + return (false); + else if (!format) + return (false); + + // Format the new string, free any old string value, and set the new value... + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + if ((s = _mxml_strcopy(buffer)) == NULL) + return (false); + + _mxml_strfree(node->value.cdata); + node->value.cdata = s; + + return (true); +} + + +// +// 'mxmlSetComment()' - Set a comment to a literal string. +// +// This function sets the string value of a comment node. +// + +bool // O - `true` on success, `false` on failure +mxmlSetComment(mxml_node_t *node, // I - Node + const char *comment) // I - Literal string +{ + char *s; // New string + + + // Range check input... + if (node && node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_COMMENT) + node = node->child; + + if (!node || node->type != MXML_TYPE_COMMENT) + return (false); + else if (!comment) + return (false); + + if (comment == node->value.comment) + return (true); + + // Free any old string value and set the new value... + if ((s = _mxml_strcopy(comment)) == NULL) + return (false); + + _mxml_strfree(node->value.comment); + node->value.comment = s; + + return (true); +} + + +// +// 'mxmlSetCommentf()' - Set a comment to a formatted string. +// +// This function sets the formatted string value of a comment node. +// + +bool // O - `true` on success, `false` on failure +mxmlSetCommentf(mxml_node_t *node, // I - Node + const char *format, // I - `printf`-style format string + ...) // I - Additional arguments as needed +{ + va_list ap; // Pointer to arguments + char buffer[16384]; // Format buffer + char *s; // Temporary string + + + // Range check input... + if (node && node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_COMMENT) + node = node->child; + + if (!node || node->type != MXML_TYPE_COMMENT) + return (false); + else if (!format) + return (false); + + // Format the new string, free any old string value, and set the new value... + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + if ((s = _mxml_strcopy(buffer)) == NULL) + return (false); + + _mxml_strfree(node->value.comment); + node->value.comment = s; + + return (true); +} + + +// +// 'mxmlSetCustom()' - Set the data and destructor of a custom data node. +// +// This function sets the data pointer `data` and destructor callback +// `destroy_cb` of a custom data node. The node is not changed if it (or its +// first child) is not a custom node. +// + +bool // O - `true` on success, `false` on failure +mxmlSetCustom( + mxml_node_t *node, // I - Node to set + void *data, // I - New data pointer + mxml_custfree_cb_t free_cb, // I - Free callback function + void *free_cbdata) // I - Free callback data +{ + // Range check input... + if (node && node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_CUSTOM) + node = node->child; + + if (!node || node->type != MXML_TYPE_CUSTOM) + return (false); + + if (data == node->value.custom.data) + goto set_free_callback; + + // Free any old element value and set the new value... + if (node->value.custom.data && node->value.custom.free_cb) + (node->value.custom.free_cb)(node->value.custom.free_cbdata, node->value.custom.data); + + node->value.custom.data = data; + + set_free_callback: + + node->value.custom.free_cb = free_cb; + node->value.custom.free_cbdata = free_cbdata; + + return (true); +} + + +// +// 'mxmlSetDeclaration()' - Set a declaration to a literal string. +// +// This function sets the string value of a declaration node. +// + +bool // O - `true` on success, `false` on failure +mxmlSetDeclaration( + mxml_node_t *node, // I - Node + const char *declaration) // I - Literal string +{ + char *s; // New string + + + // Range check input... + if (node && node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_DECLARATION) + node = node->child; + + if (!node || node->type != MXML_TYPE_DECLARATION) + return (false); + else if (!declaration) + return (false); + + if (declaration == node->value.declaration) + return (true); + + // Free any old string value and set the new value... + if ((s = _mxml_strcopy(declaration)) == NULL) + return (false); + + _mxml_strfree(node->value.declaration); + node->value.declaration = s; + + return (true); +} + + +// +// 'mxmlSetDeclarationf()' - Set a declaration to a formatted string. +// +// This function sets the formatted string value of a declaration node. +// + +bool // O - `true` on success, `false` on failure +mxmlSetDeclarationf(mxml_node_t *node, // I - Node + const char *format, // I - `printf`-style format string + ...) // I - Additional arguments as needed +{ + va_list ap; // Pointer to arguments + char buffer[16384]; // Format buffer + char *s; // Temporary string + + + // Range check input... + if (node && node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_DECLARATION) + node = node->child; + + if (!node || node->type != MXML_TYPE_DECLARATION) + return (false); + else if (!format) + return (false); + + // Format the new string, free any old string value, and set the new value... + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + if ((s = _mxml_strcopy(buffer)) == NULL) + return (false); + + _mxml_strfree(node->value.declaration); + node->value.declaration = s; + + return (true); +} + + +// +// 'mxmlSetDirective()' - Set a processing instruction to a literal string. +// +// This function sets the string value of a processing instruction node. +// + +bool // O - `true` on success, `false` on failure +mxmlSetDirective(mxml_node_t *node, // I - Node + const char *directive)// I - Literal string +{ + char *s; // New string + + + // Range check input... + if (node && node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_DIRECTIVE) + node = node->child; + + if (!node || node->type != MXML_TYPE_DIRECTIVE) + return (false); + else if (!directive) + return (false); + + if (directive == node->value.directive) + return (true); + + // Free any old string value and set the new value... + if ((s = _mxml_strcopy(directive)) == NULL) + return (false); + + _mxml_strfree(node->value.directive); + node->value.directive = s; + + return (true); +} + + +// +// 'mxmlSetDirectivef()' - Set a processing instruction to a formatted string. +// +// This function sets the formatted string value of a processing instruction +// node. +// + +bool // O - `true` on success, `false` on failure +mxmlSetDirectivef(mxml_node_t *node, // I - Node + const char *format, // I - `printf`-style format string + ...) // I - Additional arguments as needed +{ + va_list ap; // Pointer to arguments + char buffer[16384]; // Format buffer + char *s; // Temporary string + + + // Range check input... + if (node && node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_DIRECTIVE) + node = node->child; + + if (!node || node->type != MXML_TYPE_DIRECTIVE) + return (false); + else if (!format) + return (false); + + // Format the new string, free any old string value, and set the new value... + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + if ((s = _mxml_strcopy(buffer)) == NULL) + return (false); + + _mxml_strfree(node->value.directive); + node->value.directive = s; + + return (true); +} + + +// +// 'mxmlSetElement()' - Set the name of an element node. +// +// This function sets the name of an element node. The node is not changed if +// it is not an element node. +// + +bool // O - `true` on success, `false` on failure +mxmlSetElement(mxml_node_t *node, // I - Node to set + const char *name) // I - New name string +{ + char *s; // New name string + + + // Range check input... + if (!node || node->type != MXML_TYPE_ELEMENT) + return (false); + else if (!name) + return (false); + + if (name == node->value.element.name) + return (true); + + // Free any old element value and set the new value... + if ((s = _mxml_strcopy(name)) == NULL) + return (false); + + _mxml_strfree(node->value.element.name); + node->value.element.name = s; + + return (true); +} + + +// +// 'mxmlSetInteger()' - Set the value of an integer node. +// +// This function sets the value of an integer node. The node is not changed if +// it (or its first child) is not an integer node. +// + +bool // O - `true` on success, `false` on failure +mxmlSetInteger(mxml_node_t *node, // I - Node to set + long integer) // I - Integer value +{ + // Range check input... + if (node && node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_INTEGER) + node = node->child; + + if (!node || node->type != MXML_TYPE_INTEGER) + return (false); + + // Set the new value and return... + node->value.integer = integer; + + return (true); +} + + +// +// 'mxmlSetOpaque()' - Set the value of an opaque node. +// +// This function sets the string value of an opaque node. The node is not +// changed if it (or its first child) is not an opaque node. +// + +bool // O - `true` on success, `false` on failure +mxmlSetOpaque(mxml_node_t *node, // I - Node to set + const char *opaque) // I - Opaque string +{ + char *s; // New opaque string + + + // Range check input... + if (node && node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_OPAQUE) + node = node->child; + + if (!node || node->type != MXML_TYPE_OPAQUE) + return (false); + else if (!opaque) + return (false); + + if (node->value.opaque == opaque) + return (true); + + // Free any old opaque value and set the new value... + if ((s = _mxml_strcopy(opaque)) == NULL) + return (false); + + _mxml_strfree(node->value.opaque); + node->value.opaque = s; + + return (true); +} + + +// +// 'mxmlSetOpaquef()' - Set the value of an opaque string node to a formatted string. +// +// This function sets the formatted string value of an opaque node. The node is +// not changed if it (or its first child) is not an opaque node. +// + +bool // O - `true` on success, `false` on failure +mxmlSetOpaquef(mxml_node_t *node, // I - Node to set + const char *format, // I - Printf-style format string + ...) // I - Additional arguments as needed +{ + va_list ap; // Pointer to arguments + char buffer[16384]; // Format buffer + char *s; // Temporary string + + + // Range check input... + if (node && node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_OPAQUE) + node = node->child; + + if (!node || node->type != MXML_TYPE_OPAQUE) + return (false); + else if (!format) + return (false); + + // Format the new string, free any old string value, and set the new value... + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + if ((s = _mxml_strcopy(buffer)) == NULL) + return (false); + + _mxml_strfree(node->value.opaque); + node->value.opaque = s; + + return (true); +} + + +// +// 'mxmlSetReal()' - Set the value of a real value node. +// +// This function sets the value of a real value node. The node is not changed +// if it (or its first child) is not a real value node. +// + +bool // O - `true` on success, `false` on failure +mxmlSetReal(mxml_node_t *node, // I - Node to set + double real) // I - Real number value +{ + // Range check input... + if (node && node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_REAL) + node = node->child; + + if (!node || node->type != MXML_TYPE_REAL) + return (false); + + // Set the new value and return... + node->value.real = real; + + return (true); +} + + +// +// 'mxmlSetText()' - Set the value of a text node. +// +// This function sets the string and whitespace values of a text node. The node +// is not changed if it (or its first child) is not a text node. +// + +bool // O - `true` on success, `false` on failure +mxmlSetText(mxml_node_t *node, // I - Node to set + bool whitespace, // I - `true` = leading whitespace, `false` = no whitespace + const char *string) // I - String +{ + char *s; // New string + + + // Range check input... + if (node && node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_TEXT) + node = node->child; + + if (!node || node->type != MXML_TYPE_TEXT) + return (false); + else if (!string) + return (false); + + if (string == node->value.text.string) + { + node->value.text.whitespace = whitespace; + return (true); + } + + // Free any old string value and set the new value... + if ((s = _mxml_strcopy(string)) == NULL) + return (false); + + _mxml_strfree(node->value.text.string); + + node->value.text.whitespace = whitespace; + node->value.text.string = s; + + return (true); +} + + +// +// 'mxmlSetTextf()' - Set the value of a text node to a formatted string. +// +// This function sets the formatted string and whitespace values of a text node. +// The node is not changed if it (or its first child) is not a text node. +// + +bool // O - `true` on success, `false` on failure +mxmlSetTextf(mxml_node_t *node, // I - Node to set + bool whitespace, // I - `true` = leading whitespace, `false` = no whitespace + const char *format, // I - Printf-style format string + ...) // I - Additional arguments as needed +{ + va_list ap; // Pointer to arguments + char buffer[16384]; // Format buffer + char *s; // Temporary string + + + // Range check input... + if (node && node->type == MXML_TYPE_ELEMENT && node->child && node->child->type == MXML_TYPE_TEXT) + node = node->child; + + if (!node || node->type != MXML_TYPE_TEXT) + return (false); + else if (!format) + return (false); + + // Free any old string value and set the new value... + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + if ((s = _mxml_strcopy(buffer)) == NULL) + return (false); + + _mxml_strfree(node->value.text.string); + + node->value.text.whitespace = whitespace; + node->value.text.string = s; + + return (true); +} + + +// +// 'mxmlSetUserData()' - Set the user data pointer for a node. +// + +bool // O - `true` on success, `false` on failure +mxmlSetUserData(mxml_node_t *node, // I - Node to set + void *data) // I - User data pointer +{ + // Range check input... + if (!node) + return (false); + + // Set the user data pointer and return... + node->user_data = data; + return (true); +} diff --git a/src/main/mxml/mxml.h b/src/main/mxml/mxml.h new file mode 100644 index 0000000..b97f446 --- /dev/null +++ b/src/main/mxml/mxml.h @@ -0,0 +1,250 @@ +// +// Header file for Mini-XML, a small XML file parsing library. +// +// https://www.msweet.org/mxml +// +// Copyright © 2003-2024 by Michael R Sweet. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#ifndef MXML_H +# define MXML_H +# include +# include +# include +# include +# include +# include +# include +# include +# ifdef __cplusplus +extern "C" { +# endif // __cplusplus + + +// +// Constants... +// + +# define MXML_MAJOR_VERSION 4 // Major version number +# define MXML_MINOR_VERSION 0 // Minor version number + +# ifdef __GNUC__ +# define MXML_FORMAT(a,b) __attribute__ ((__format__ (__printf__, a, b))) +# else +# define MXML_FORMAT(a,b) +# endif // __GNUC__ + + +// +// Data types... +// + +typedef enum mxml_add_e // @link mxmlAdd@ add values +{ + MXML_ADD_BEFORE, // Add node before specified node + MXML_ADD_AFTER // Add node after specified node +} mxml_add_t; + +typedef enum mxml_descend_e // @link mxmlFindElement@, @link mxmlWalkNext@, and @link mxmlWalkPrev@ descend values +{ + MXML_DESCEND_FIRST = -1, // Descend for first find + MXML_DESCEND_NONE = 0, // Don't descend when finding/walking + MXML_DESCEND_ALL = 1 // Descend when finding/walking +} mxml_descend_t; + +typedef enum mxml_sax_event_e // SAX event type. +{ + MXML_SAX_EVENT_CDATA, // CDATA node + MXML_SAX_EVENT_COMMENT, // Comment node + MXML_SAX_EVENT_DATA, // Data node + MXML_SAX_EVENT_DECLARATION, // Declaration node + MXML_SAX_EVENT_DIRECTIVE, // Processing instruction node + MXML_SAX_EVENT_ELEMENT_CLOSE, // Element closed + MXML_SAX_EVENT_ELEMENT_OPEN // Element opened +} mxml_sax_event_t; + +typedef enum mxml_type_e // The XML node type. +{ + MXML_TYPE_IGNORE = -1, // Ignore/throw away node + MXML_TYPE_CDATA, // CDATA value ("<[CDATA[...]]>") + MXML_TYPE_COMMENT, // Comment ("") + MXML_TYPE_DECLARATION, // Declaration ("") + MXML_TYPE_DIRECTIVE, // Processing instruction ("") + MXML_TYPE_ELEMENT, // XML element with attributes + MXML_TYPE_INTEGER, // Integer value + MXML_TYPE_OPAQUE, // Opaque string + MXML_TYPE_REAL, // Real value + MXML_TYPE_TEXT, // Text fragment + MXML_TYPE_CUSTOM // Custom data +} mxml_type_t; + +typedef enum mxml_ws_e // Whitespace periods +{ + MXML_WS_BEFORE_OPEN, // Callback for before open tag + MXML_WS_AFTER_OPEN, // Callback for after open tag + MXML_WS_BEFORE_CLOSE, // Callback for before close tag + MXML_WS_AFTER_CLOSE, // Callback for after close tag +} mxml_ws_t; + +typedef void (*mxml_error_cb_t)(void *cbdata, const char *message); + // Error callback function + +typedef struct _mxml_node_s mxml_node_t;// An XML node + +typedef struct _mxml_index_s mxml_index_t; + // An XML node index + +typedef struct _mxml_options_s mxml_options_t; + // XML options + +typedef void (*mxml_custfree_cb_t)(void *cbdata, void *custdata); + // Custom data destructor + +typedef bool (*mxml_custload_cb_t)(void *cbdata, mxml_node_t *node, const char *s); + // Custom data load callback function + +typedef char *(*mxml_custsave_cb_t)(void *cbdata, mxml_node_t *node); + // Custom data save callback function + +typedef int (*mxml_entity_cb_t)(void *cbdata, const char *name); + // Entity callback function + +typedef size_t (*mxml_io_cb_t)(void *cbdata, void *buffer, size_t bytes); + // Read/write callback function + +typedef bool (*mxml_sax_cb_t)(void *cbdata, mxml_node_t *node, mxml_sax_event_t event); + // SAX 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 mxml_type_t (*mxml_type_cb_t)(void *cbdata, mxml_node_t *node); + // Type callback function + +typedef const char *(*mxml_ws_cb_t)(void *cbdata, mxml_node_t *node, mxml_ws_t when); + // Whitespace callback function + + + +// +// Prototypes... +// + +extern void mxmlAdd(mxml_node_t *parent, mxml_add_t add, mxml_node_t *child, mxml_node_t *node); + +extern void mxmlDelete(mxml_node_t *node); + +extern void mxmlElementClearAttr(mxml_node_t *node, const char *name); +extern const char *mxmlElementGetAttr(mxml_node_t *node, const char *name); +extern const char *mxmlElementGetAttrByIndex(mxml_node_t *node, size_t idx, const char **name); +extern size_t mxmlElementGetAttrCount(mxml_node_t *node); +extern void mxmlElementSetAttr(mxml_node_t *node, const char *name, const char *value); +extern void mxmlElementSetAttrf(mxml_node_t *node, const char *name, const char *format, ...) MXML_FORMAT(3,4); + +extern mxml_node_t *mxmlFindElement(mxml_node_t *node, mxml_node_t *top, const char *element, const char *attr, const char *value, mxml_descend_t descend); +extern mxml_node_t *mxmlFindPath(mxml_node_t *node, const char *path); + +extern const char *mxmlGetCDATA(mxml_node_t *node); +extern const char *mxmlGetComment(mxml_node_t *node); +extern const void *mxmlGetCustom(mxml_node_t *node); +extern const char *mxmlGetDeclaration(mxml_node_t *node); +extern const char *mxmlGetDirective(mxml_node_t *node); +extern const char *mxmlGetElement(mxml_node_t *node); +extern mxml_node_t *mxmlGetFirstChild(mxml_node_t *node); +extern long mxmlGetInteger(mxml_node_t *node); +extern mxml_node_t *mxmlGetLastChild(mxml_node_t *node); +extern mxml_node_t *mxmlGetNextSibling(mxml_node_t *node); +extern const char *mxmlGetOpaque(mxml_node_t *node); +extern mxml_node_t *mxmlGetParent(mxml_node_t *node); +extern mxml_node_t *mxmlGetPrevSibling(mxml_node_t *node); +extern double mxmlGetReal(mxml_node_t *node); +extern size_t mxmlGetRefCount(mxml_node_t *node); +extern const char *mxmlGetText(mxml_node_t *node, bool *whitespace); +extern mxml_type_t mxmlGetType(mxml_node_t *node); +extern void *mxmlGetUserData(mxml_node_t *node); + +extern void mxmlIndexDelete(mxml_index_t *ind); +extern mxml_node_t *mxmlIndexEnum(mxml_index_t *ind); +extern mxml_node_t *mxmlIndexFind(mxml_index_t *ind, const char *element, const char *value); +extern size_t mxmlIndexGetCount(mxml_index_t *ind); +extern mxml_index_t *mxmlIndexNew(mxml_node_t *node, const char *element, const char *attr); +extern mxml_node_t *mxmlIndexReset(mxml_index_t *ind); + +extern mxml_node_t *mxmlLoadFd(mxml_node_t *top, mxml_options_t *options, int fd); +extern mxml_node_t *mxmlLoadFile(mxml_node_t *top, mxml_options_t *options, FILE *fp); +extern mxml_node_t *mxmlLoadFilename(mxml_node_t *top, mxml_options_t *options, const char *filename); +extern mxml_node_t *mxmlLoadIO(mxml_node_t *top, mxml_options_t *options, mxml_io_cb_t io_cb, void *io_cbdata); +extern mxml_node_t *mxmlLoadString(mxml_node_t *top, mxml_options_t *options, const char *s); + +extern void mxmlOptionsDelete(mxml_options_t *options); +extern mxml_options_t *mxmlOptionsNew(void); +extern void mxmlOptionsSetCustomCallbacks(mxml_options_t *options, mxml_custload_cb_t load_cb, mxml_custsave_cb_t save_cb, void *cbdata); +extern void mxmlOptionsSetEntityCallback(mxml_options_t *options, mxml_entity_cb_t cb, void *cbdata); +extern void mxmlOptionsSetErrorCallback(mxml_options_t *options, mxml_error_cb_t cb, void *cbdata); +extern void mxmlOptionsSetSAXCallback(mxml_options_t *options, mxml_sax_cb_t cb, void *cbdata); +extern void mxmlOptionsSetTypeCallback(mxml_options_t *options, mxml_type_cb_t cb, void *cbdata); +extern void mxmlOptionsSetTypeValue(mxml_options_t *options, mxml_type_t type); +extern void mxmlOptionsSetWhitespaceCallback(mxml_options_t *options, mxml_ws_cb_t cb, void *cbdata); +extern void mxmlOptionsSetWrapMargin(mxml_options_t *options, int column); + +extern mxml_node_t *mxmlNewCDATA(mxml_node_t *parent, const char *string); +extern mxml_node_t *mxmlNewCDATAf(mxml_node_t *parent, const char *format, ...) MXML_FORMAT(2,3); +extern mxml_node_t *mxmlNewComment(mxml_node_t *parent, const char *comment); +extern mxml_node_t *mxmlNewCommentf(mxml_node_t *parent, const char *format, ...) MXML_FORMAT(2,3); +extern mxml_node_t *mxmlNewCustom(mxml_node_t *parent, void *data, mxml_custfree_cb_t free_cb, void *free_cbdata); +extern mxml_node_t *mxmlNewDeclaration(mxml_node_t *parent, const char *declaration); +extern mxml_node_t *mxmlNewDeclarationf(mxml_node_t *parent, const char *format, ...) MXML_FORMAT(2,3); +extern mxml_node_t *mxmlNewDirective(mxml_node_t *parent, const char *directive); +extern mxml_node_t *mxmlNewDirectivef(mxml_node_t *parent, const char *format, ...) MXML_FORMAT(2,3); +extern mxml_node_t *mxmlNewElement(mxml_node_t *parent, const char *name); +extern mxml_node_t *mxmlNewInteger(mxml_node_t *parent, long integer); +extern mxml_node_t *mxmlNewOpaque(mxml_node_t *parent, const char *opaque); +extern mxml_node_t *mxmlNewOpaquef(mxml_node_t *parent, const char *format, ...) MXML_FORMAT(2,3); +extern mxml_node_t *mxmlNewReal(mxml_node_t *parent, double real); +extern mxml_node_t *mxmlNewText(mxml_node_t *parent, bool whitespace, const char *string); +extern mxml_node_t *mxmlNewTextf(mxml_node_t *parent, bool whitespace, const char *format, ...) MXML_FORMAT(3,4); +extern mxml_node_t *mxmlNewXML(const char *version); + +extern int mxmlRelease(mxml_node_t *node); +extern void mxmlRemove(mxml_node_t *node); +extern int mxmlRetain(mxml_node_t *node); + +extern char *mxmlSaveAllocString(mxml_node_t *node, mxml_options_t *options); +extern bool mxmlSaveFd(mxml_node_t *node, mxml_options_t *options, int fd); +extern bool mxmlSaveFile(mxml_node_t *node, mxml_options_t *options, FILE *fp); +extern bool mxmlSaveFilename(mxml_node_t *node, mxml_options_t *options, const char *filename); +extern bool mxmlSaveIO(mxml_node_t *node, mxml_options_t *options, mxml_io_cb_t io_cb, void *io_cbdata); +extern size_t mxmlSaveString(mxml_node_t *node, mxml_options_t *options, char *buffer, size_t bufsize); + +extern bool mxmlSetCDATA(mxml_node_t *node, const char *data); +extern bool mxmlSetCDATAf(mxml_node_t *node, const char *format, ...) MXML_FORMAT(2,3); +extern bool mxmlSetComment(mxml_node_t *node, const char *comment); +extern bool mxmlSetCommentf(mxml_node_t *node, const char *format, ...) MXML_FORMAT(2,3); +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_custfree_cb_t free_cb, void *free_cbdata); +extern bool mxmlSetElement(mxml_node_t *node, const char *name); +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); + +extern mxml_node_t *mxmlWalkNext(mxml_node_t *node, mxml_node_t *top, mxml_descend_t descend); +extern mxml_node_t *mxmlWalkPrev(mxml_node_t *node, mxml_node_t *top, mxml_descend_t descend); + + +# ifdef __cplusplus +} +# endif // __cplusplus +#endif // !MXML_H