/*------------------------------------------------------------------
* xml_* Efuns
*
* Based on code written and donated 2009 by Heiko Kopp.
*------------------------------------------------------------------
* This file holds the efuns interfacing with libxml2 and provides
* functions for handling xml files and converting them between
* mappings and xml data strings.
*
* efun: xml_
*------------------------------------------------------------------
*/
#include "driver.h"
#include "machine.h"
#if defined(USE_XML) && defined(HAS_XML2)
#include <libxml/parser.h>
#include <libxml/xmlwriter.h>
#include <libxml/xmlreader.h>
#include "array.h"
#include "arraylist.h"
#include "xalloc.h"
#include "mapping.h"
#include "mstrings.h"
#include "simulate.h"
#include "interpret.h"
#include "pkg-xml2.h"
#include "typedefs.h"
#include "../mudlib/sys/xml.h"
/* Structure to walk over the attribute (properties in libxml2 jargon) to
* create property-nodes
*/
typedef struct attribute_walk_extra_s attribute_walk_extra_t;
/* Used to walk all attributes as well as for error handling. In case an
* error happens, this structure is called too.
*/
struct attribute_walk_extra_s
{
xmlTextWriterPtr writer;
char *tag_name;
};
/* Used for error handling. In case of an error our handler called with a
* pointer ot this structure.
*/
struct xml_cleanup_s
{
svalue_t head; /* push_error_handler saves the link to our handler here. */
xmlTextReaderPtr reader;
xmlTextWriterPtr writer;
xmlBufferPtr buf;
};
static void *
xml_pkg_malloc (size_t size)
/*
* Realize malloc with the driver-internal xalloc rather than a direct malloc()
*/
{
return xalloc(size);
}
static void
xml_pkg_free (void * ptr)
/*
* Realize free with the driver-internal xfree rather than a direct free()
*/
{
xfree(ptr);
}
static void *
xml_pkg_realloc (void * ptr, size_t size)
/*
* Realize realloc() with the driver-internal rexalloc_traced() including file
* and line rather the direct realloc()
*/
{
return rexalloc(ptr, size);
}
static char *
xml_pkg_strdup (const char * str)
/*
* Realize strdup with the driver interal string_copy instead of the direct
* strdup()
*/
{
return string_copy(str);
}
static void
add_string_to_mapping (mapping_t *map, const char *skey, const char *svalue)
/*
* Adds a string value under the given key to the given mapping. In case the
* value already exists, it is overriden.
*/
{
svalue_t key;
svalue_t *value;
/* change the c string into an string_t */
put_c_string(&key, skey);
/* get or insert key */
value = get_map_lvalue(map, &key);
/* free the string_t again */
free_svalue(&key);
/* free maybe existing value (should not happen, i hope) */
free_svalue(value);
/* change the value of the key to the given value */
put_c_string(value, svalue);
}
static void
parse_node (svalue_t *result, xmlTextReaderPtr reader)
/*
* Parses the xml document beginning at <node> and returns the information
* on the stack in <result>.
*/
{
vector_t *element = NULL;
svalue_t *children = NULL;
/* We have a tag here, so allocate a tag array with three elements
* (name, contents and attributes)
*/
memsafe(element = allocate_array(XML_TAG_SIZE), sizeof(*element)
, "new tag array");
/* Put the array as result */
put_array(result, element);
/* add name to array */
put_c_string(&element->item[XML_TAG_NAME]
, (const char *) xmlTextReaderConstName(reader));
if (xmlTextReaderHasAttributes(reader))
{
mapping_t *attributes = NULL;
/* allocate new mapping */
memsafe(attributes = allocate_mapping(xmlTextReaderAttributeCount(reader), 1)
, sizeof(*attributes), "new attributes mapping");
/* add the attributes to the array */
put_mapping(&element->item[XML_TAG_ATTRIBUTES], attributes);
while (MY_TRUE)
{
int ret;
ret = xmlTextReaderMoveToNextAttribute(reader);
if (ret == 0)
break;
else if (ret < 0)
errorf("(xml_parse) Error reading XML node.\n");
add_string_to_mapping(attributes
, (const char *) xmlTextReaderConstName(reader)
, (const char *) xmlTextReaderConstValue(reader));
}
xmlTextReaderMoveToElement(reader);
}
if (!xmlTextReaderIsEmptyElement(reader))
while (MY_TRUE)
{
int ret;
int is_node = 0;
ret = xmlTextReaderRead(reader);
if (ret == 0)
errorf("Bad arg 1 to xml_parse(): Premature end of data.\n");
else if(ret < 0)
errorf("(xml_parse) Error reading XML node.\n");
switch (xmlTextReaderNodeType(reader))
{
case XML_READER_TYPE_END_ELEMENT:
if (children != NULL)
finalize_arraylist(children);
return;
case XML_READER_TYPE_ELEMENT:
is_node = 1;
/* FALLTHROUGH */
case XML_READER_TYPE_TEXT:
case XML_READER_TYPE_CDATA:
if (children == NULL)
{
children = &(element->item[XML_TAG_CONTENTS]);
put_arraylist(children);
}
if (is_node)
parse_node(enhance_arraylist(children), reader);
else
put_c_string(enhance_arraylist(children)
, (const char *) xmlTextReaderConstValue(reader));
break;
}
}
}
static void
walk_attribute_mapping(svalue_t *key, svalue_t *val, void *pextra)
/*
* Callback for walk_mapping() used in generate_xml_node to add
* property-nodes to the node given in the <pextra>.
*/
{
attribute_walk_extra_t *extra = pextra;
int rc;
if (key->type != T_STRING)
errorf("Bad argument 1 to xml_generate(): expected string \
for attribute key of tag '%s'.\n", extra->tag_name);
if (val->type != T_STRING)
errorf("Bad argument 1 to xml_generate(): expected string for \
value of attribute '%s' of tag '%s'.\n"
, get_txt(key->u.str), extra->tag_name);
rc = xmlTextWriterWriteAttribute( extra->writer
, (xmlChar *) get_txt(key->u.str)
, (xmlChar *) get_txt(val->u.str));
if (rc < 0)
errorf("(xml_generate) Error writing attribute.\n");
}
static void
xml_cleanup (svalue_t * arg)
/*
* Takes care, that the xml document is correctly freed in case of an error
* and at the end of the f_generate_xml(). Additionally the xml-parser
* is cleaned up
*/
{
struct xml_cleanup_s * data;
data = (struct xml_cleanup_s *) arg;
if (data->buf)
{
xmlBufferFree(data->buf);
}
if (data->writer)
{
xmlFreeTextWriter(data->writer);
}
if (data->reader)
{
xmlFreeTextReader(data->reader);
}
xmlCleanupParser();
xfree(data);
} /* xml_cleanup() */
static void
write_xml_node(vector_t * vnode, xmlTextWriterPtr writer)
/* Writes a new xml node from the given array structure with the three
* elements (name, contents, attributes) using <writer>.
* The contents element may contain other tags, so recursion may occur.
*/
{
svalue_t *element;
char *name;
int rc;
if ((mp_int) VEC_SIZE(vnode) != 3)
{
errorf("Bad arg 1 to xml_generate(): tag is not an array with 3 \
elements.\n");
/* NOTREACHED */
return;
}
/* get the name, as this is essential */
element = &vnode->item[XML_TAG_NAME];
if (element->type != T_STRING)
{
errorf("Bad arg 1 to xml_generate(): first element of tag array \
not a string.\n");
/* NOTREACHED */
return;
}
name = get_txt(element->u.str);
rc = xmlTextWriterStartElement(writer, (xmlChar *) name);
if (rc < 0)
errorf("(xml_generate) Error writing XML element.\n");
/* now handle the attributes of this one */
element = &vnode->item[XML_TAG_ATTRIBUTES];
/* this might be absent */
if (element->type == T_MAPPING)
{
attribute_walk_extra_t extra;
extra.writer = writer;
extra.tag_name = name;
/* walk the mapping and add all attributes */
walk_mapping(element->u.map, &walk_attribute_mapping, &extra);
}
else if (element->type != T_NUMBER || element->u.number != 0)
{
errorf("Bad arg 1 to xml_generate(): second element of tag array not "
"NULL/mapping.\n");
/* NOTREACHED */
return;
}
/* now check, if the node has a contents */
element = &vnode->item[XML_TAG_CONTENTS];
/* this might even be absent */
if (element->type == T_POINTER)
{
int size;
int i;
vector_t *contents;
/* get the vector */
contents = element->u.vec;
/* get its size */
size = (mp_int)VEC_SIZE(contents);
for (i = 0; i < size; i++)
{
element = &contents->item[i];
if (element->type == T_STRING)
{
/* found content */
rc = xmlTextWriterWriteString(writer, (xmlChar *) get_txt(element->u.str));
if (rc < 0)
errorf("(xml_generate) Error writing plain text.\n");
}
else if (element->type == T_POINTER)
{
/* found a sub tag */
write_xml_node(element->u.vec, writer);
}
}
}
else if (element->type != T_NUMBER || element->u.number != 0)
{
errorf("Bad arg 1 to xml_generate(): third element of tag array not "
"NULL/array.\n");
/* NOTREACHED */
}
rc = xmlTextWriterEndElement(writer);
if (rc < 0)
errorf("(xml_generate) Error finishing XML element.\n");
}
void
pkg_xml2_init ()
{
/* Check for correct libxml version. */
LIBXML_TEST_VERSION
xmlMemSetup(xml_pkg_free, xml_pkg_malloc, xml_pkg_realloc, xml_pkg_strdup);
}
/*=========================================================================*/
/* EFUNS */
/*-------------------------------------------------------------------------*/
svalue_t *
f_xml_generate (svalue_t *sp)
/* EFUN xml_generate()
*
* string xml_generate(mixed *xml)
*
* Converts the given <xml> array into an XML conform string, if
* possible. The <xml> argument array must have the same structure
* as xml_parse returns.
*
* In case the parameter does not follow these rules, errors are raised.
* The method returns a valid XML string otherwise.
*/
{
struct xml_cleanup_s * rec_data;
int rc;
memsafe(rec_data = xalloc(sizeof(*rec_data)), sizeof(*rec_data)
, "xml cleanup structure");
rec_data->buf = NULL;
rec_data->writer = NULL;
rec_data->reader = NULL;
push_error_handler(xml_cleanup, &(rec_data->head));
/* the output buffer. */
rec_data->buf = xmlBufferCreate();
if (rec_data->buf == NULL)
errorf("(xml_generate) Out of memory: temporary buffer.\n");
rec_data->writer = xmlNewTextWriterMemory(rec_data->buf, 0);
if (rec_data->writer == NULL)
errorf("(xml_generate) Out of memory: XML writer.\n");
rc = xmlTextWriterStartDocument(rec_data->writer, NULL, NULL, NULL);
if (rc < 0)
errorf("(xml_generate) Error starting XML document.\n");
write_xml_node(sp->u.vec, rec_data->writer);
rc = xmlTextWriterEndDocument(rec_data->writer);
if (rc < 0)
errorf("(xml_generate) Error finishing XML document.\n");
/* Free the array. */
free_svalue(sp);
put_c_string(sp, (char *) rec_data->buf->content);
/* The error handler will free the buffer
* and XML writer.
*/
pop_stack();
return sp;
}
static void
xml_pkg_error_handler(void * userData, xmlErrorPtr error)
{
if (error)
{
errorf("Bad arg 1 to xml_parse(): %s", error->message);
}
}
svalue_t *
f_xml_parse(svalue_t * sp)
/* EFUN xml_parse()
*
* mixed * xml_parse(string xml_text)
*
* Parses the given string <xml> as a XML conform string. The string must
* have only one root tag, subsequent root tags are ignored.
*
* If the xml string is correct, an array is of three elements is
* returned, where as the following indices are defined:
*
* string XML_TAG_NAME
* The name of the XML tag.
*
* mixed * XML_TAG_CONTENTS
* The contents of this xml tag as array. This array may
* contain either strings, or arrags of sub-tags again with
* three elements (see example)
*
* If the xml tag does not contain anything, the element is
* set 0.
*
* mapping XML_TAG_ATTRIBUTES
* All attributes given to the XML tag as mapping where the key
* is the attribute name and the value is its string value.
*
* If the xml tag does not contain any attributes, this element
* is set 0.
*
* If the XML string is not well formed, or there is not enough memory to
* parse the whole XML structure into the array an error is raised. In case
* the XML string can't be parsed, cause it is not valid XML, 0 is returned.
*/
{
struct xml_cleanup_s * rec_data;
memsafe(rec_data = xalloc(sizeof(*rec_data)), sizeof(*rec_data)
, "xml cleanup structure");
rec_data->buf = NULL;
rec_data->writer = NULL;
rec_data->reader = NULL;
push_error_handler(xml_cleanup, &(rec_data->head));
xmlInitParser();
/* Disable standard error functions, as they will print out errors
* to stderr automatically. We do not want that.
*/
xmlSetGenericErrorFunc(NULL, NULL);
xmlSetStructuredErrorFunc(NULL, xml_pkg_error_handler);
rec_data->reader = xmlReaderForMemory(get_txt(sp->u.str)
, mstrsize(sp->u.str)
, NULL, NULL, XML_PARSE_NOENT);
if (rec_data->reader == NULL)
errorf("(xml_generate) Out of memory: XML reader.\n");
/* Put the result on top of the stack. */
push_number(inter_sp, 0);
/* Look for the first element. */
do
{
int ret;
ret = xmlTextReaderRead(rec_data->reader);
if (ret == 0)
errorf("Bad arg 1 to xml_parse(): Premature end of data.\n");
else if(ret < 0)
errorf("(xml_parse) Error reading XML node.\n");
switch (xmlTextReaderNodeType(rec_data->reader))
{
case XML_READER_TYPE_ATTRIBUTE:
case XML_READER_TYPE_TEXT:
case XML_READER_TYPE_CDATA:
errorf("Bad arg 1 to xml_parse(): Start tag expected.\n");
case XML_READER_TYPE_ELEMENT:
break;
default:
continue;
}
} while (MY_FALSE);
/* Now parse the XML string. */
parse_node(inter_sp, rec_data->reader);
/* we no longer need the string */
free_svalue(sp);
*sp = *inter_sp;
inter_sp--;
/* At the end, be nice and remove the rest using our error handler. */
pop_stack();
return sp;
}
#endif /* USE_XML && HAS_XML2 */