mirror of git://git.psyced.org/git/psyclpc
561 lines
14 KiB
C
561 lines
14 KiB
C
|
/*------------------------------------------------------------------
|
||
|
* iksemel Efuns
|
||
|
*
|
||
|
*------------------------------------------------------------------
|
||
|
* This file holds the efuns interfacing with iksemel and provides
|
||
|
* functions for handling xml files and converting them between
|
||
|
* mappings and xml data strings.
|
||
|
*
|
||
|
* efun: xml_
|
||
|
*------------------------------------------------------------------
|
||
|
*/
|
||
|
#include "driver.h"
|
||
|
|
||
|
#ifdef USE_IKSEMEL
|
||
|
|
||
|
#include <iksemel.h>
|
||
|
#include "array.h"
|
||
|
#include "xalloc.h"
|
||
|
#include "mapping.h"
|
||
|
#include "mstrings.h"
|
||
|
#include "simulate.h"
|
||
|
#include "interpret.h"
|
||
|
#include "pkg-iksemel.h"
|
||
|
#include "typedefs.h"
|
||
|
|
||
|
#include "../mudlib/sys/xml.h"
|
||
|
|
||
|
typedef struct attribute_walk_extra_s attribute_walk_extra_t;
|
||
|
|
||
|
/* This structure is 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
|
||
|
{
|
||
|
iks *node;
|
||
|
int size;
|
||
|
|
||
|
char *tag_name;
|
||
|
};
|
||
|
|
||
|
/* This structure is 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. */
|
||
|
|
||
|
iks *node;
|
||
|
iksparser *parser;
|
||
|
};
|
||
|
|
||
|
void *
|
||
|
iksemel_alloc(size_t size)
|
||
|
{
|
||
|
return xalloc(size);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
iksemel_free(void *ptr)
|
||
|
{
|
||
|
xfree(ptr);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
add_string_to_mapping(mapping_t *map, char *skey, 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);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
parse_node(svalue_t *result, iks *node)
|
||
|
|
||
|
/*
|
||
|
* Parses the xml DOM starting at node and returns the information on the
|
||
|
* stack.
|
||
|
*/
|
||
|
{
|
||
|
int i = 0;
|
||
|
int num_attributes = 0;
|
||
|
int num_children = 0;
|
||
|
|
||
|
iks *attribute;
|
||
|
iks *child;
|
||
|
|
||
|
vector_t *root = NULL;
|
||
|
vector_t *children = NULL;
|
||
|
mapping_t *attributes = NULL;
|
||
|
|
||
|
/* lets figure out if the node is cdata or another tag */
|
||
|
switch (iks_type(node))
|
||
|
{
|
||
|
case IKS_NONE:
|
||
|
case IKS_ATTRIBUTE:
|
||
|
/* we ignore those, as they will not occure for us */
|
||
|
|
||
|
return;
|
||
|
case IKS_CDATA:
|
||
|
/* Add the string as result, and return */
|
||
|
put_c_n_string(result, iks_cdata(node), iks_cdata_size(node));
|
||
|
|
||
|
return;
|
||
|
case IKS_TAG:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* We have a tag here, so allocate a tag array with three elements
|
||
|
* (name, contents and attributes)
|
||
|
*/
|
||
|
memsafe(root = allocate_array(XML_TAG_SIZE), sizeof(*root), "new tag array");
|
||
|
|
||
|
/* Put the array as result */
|
||
|
put_array(result, root);
|
||
|
|
||
|
/* add name to array */
|
||
|
put_c_string(&root->item[XML_TAG_NAME], iks_name(node));
|
||
|
|
||
|
/* check if the node has any children */
|
||
|
child = iks_child(node);
|
||
|
if (child != NULL)
|
||
|
{
|
||
|
do
|
||
|
{
|
||
|
++num_children;
|
||
|
}
|
||
|
while ((child = iks_next(child)));
|
||
|
}
|
||
|
|
||
|
if (0 < num_children)
|
||
|
{
|
||
|
/* children need to stay in the right order, so we create another
|
||
|
* for them
|
||
|
*/
|
||
|
memsafe(children = allocate_array(num_children), sizeof(*children)
|
||
|
, "new tag contents array");
|
||
|
|
||
|
/* Add the array of all children to the node */
|
||
|
put_array(&root->item[XML_TAG_CONTENTS], children);
|
||
|
|
||
|
/* get the first child */
|
||
|
child = iks_child(node);
|
||
|
|
||
|
do
|
||
|
{
|
||
|
/* recurse here cause the child can be a string or another node */
|
||
|
parse_node(&children->item[i++], child);
|
||
|
}
|
||
|
while ((child = iks_next(child)));
|
||
|
}
|
||
|
|
||
|
/* Finally, lets handle the attributes, we need to find out how many
|
||
|
* attributes the node has, to allocate enough memory for them. If
|
||
|
* no attributes exist, the part in the array will be empty */
|
||
|
|
||
|
attribute = iks_attrib(node);
|
||
|
if (attribute != NULL)
|
||
|
{
|
||
|
do
|
||
|
{
|
||
|
++num_attributes;
|
||
|
}
|
||
|
while ((attribute = iks_next(attribute)));
|
||
|
}
|
||
|
|
||
|
if (0 < num_attributes)
|
||
|
{
|
||
|
/* allocate new mapping */
|
||
|
memsafe(attributes = allocate_mapping(num_attributes, 1), sizeof(*attributes)
|
||
|
, "new attributes mapping");
|
||
|
|
||
|
/* add the attributes to the array */
|
||
|
put_mapping(&root->item[XML_TAG_ATTRIBUTES], attributes);
|
||
|
|
||
|
/* get the first one */
|
||
|
attribute = iks_attrib(node);
|
||
|
|
||
|
do
|
||
|
{
|
||
|
add_string_to_mapping(attributes, iks_name(attribute), iks_cdata(attribute));
|
||
|
}
|
||
|
while ((attribute = iks_next(attribute)));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
walk_attribute_mapping(svalue_t *key, svalue_t *val, void *pextra)
|
||
|
|
||
|
/*
|
||
|
* Callback for walk_mapping() used in generate_xml_node to add iks
|
||
|
* attribute nodes to the node given in the pextra.
|
||
|
*/
|
||
|
{
|
||
|
char *ckey;
|
||
|
attribute_walk_extra_t *extra = pextra;
|
||
|
|
||
|
if (key->type == T_STRING)
|
||
|
{
|
||
|
ckey = get_txt(key->u.str);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
errorf("Bad argument 1 to xml_generate(): expected string for attribute key of tag '%s'.\n"
|
||
|
, extra->tag_name);
|
||
|
|
||
|
/* NOTREACHED */
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (val->type == T_STRING)
|
||
|
{
|
||
|
memsafe(iks_insert_attrib(extra->node, ckey, get_txt(val->u.str))
|
||
|
, sizeof(*ckey), "new iksemel attribute");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
errorf("Bad argument 1 to xml_generate(): expected string for value of attribute '%s' of tag '%s'.\n"
|
||
|
, ckey, extra->tag_name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
xml_cleanup(svalue_t * arg)
|
||
|
|
||
|
/*
|
||
|
* Takes care, that the node without parent (root node) is correctly freed in
|
||
|
* case of an error and at the end of the f_generate_xml().
|
||
|
*/
|
||
|
{
|
||
|
struct xml_cleanup_s * data;
|
||
|
|
||
|
data = (struct xml_cleanup_s *)arg;
|
||
|
|
||
|
if (data->node)
|
||
|
{
|
||
|
iks_delete(data->node);
|
||
|
}
|
||
|
|
||
|
if (data->parser)
|
||
|
{
|
||
|
iks_parser_delete(data->parser);
|
||
|
}
|
||
|
|
||
|
xfree(data);
|
||
|
} /* xml_cleanup() */
|
||
|
|
||
|
iks *
|
||
|
generate_xml_node(vector_t *vnode, iks *parent)
|
||
|
|
||
|
/*
|
||
|
* Generates a new iks node from the given array structure with the three
|
||
|
* elements (name, contents, attributes) and adds the node to the parent,
|
||
|
* or in case this is empty, simply returns it. The contents element may
|
||
|
* contain other tags, so recursion may occur.
|
||
|
*/
|
||
|
{
|
||
|
iks *node;
|
||
|
svalue_t *element;
|
||
|
char *name;
|
||
|
struct xml_cleanup_s * rec_data;
|
||
|
|
||
|
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 NULL;
|
||
|
}
|
||
|
|
||
|
/* 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 NULL;
|
||
|
}
|
||
|
|
||
|
/* get the name */
|
||
|
name = get_txt(element->u.str);
|
||
|
|
||
|
/* depending whether there is a parent or not, we start the structure
|
||
|
* or add the node to the given one */
|
||
|
if (parent == NULL)
|
||
|
{
|
||
|
memsafe(node = iks_new(name), 30, "new iksemel node");
|
||
|
|
||
|
rec_data = xalloc(sizeof(*rec_data));
|
||
|
if (rec_data == NULL)
|
||
|
{
|
||
|
iks_delete(node);
|
||
|
|
||
|
errorf("generate_xml() Out of memory: (%lu bytes) for cleanup structure\n"
|
||
|
, (unsigned long) sizeof(*rec_data));
|
||
|
|
||
|
/* NOTREACHED */
|
||
|
return NULL;
|
||
|
}
|
||
|
rec_data->node = node;
|
||
|
rec_data->parser = NULL;
|
||
|
|
||
|
push_error_handler(xml_cleanup, &(rec_data->head));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
memsafe(node = iks_insert(parent, name), 30, "insert new iksemel node");
|
||
|
}
|
||
|
|
||
|
/* 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.node = node;
|
||
|
extra.size = element->u.map->num_values;
|
||
|
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 NULL;
|
||
|
}
|
||
|
|
||
|
/* 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 a cdata */
|
||
|
memsafe(iks_insert_cdata(node, get_txt(element->u.str), mstrsize(element->u.str))
|
||
|
, mstrsize(element->u.str)
|
||
|
, "new iksemel node cdata");
|
||
|
}
|
||
|
else if (element->type == T_POINTER)
|
||
|
{
|
||
|
/* found a sub tag, as iks_insert will handle the insert we do
|
||
|
* not have anything to do with the result
|
||
|
*/
|
||
|
generate_xml_node(element->u.vec, node);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
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 */
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
pkg_iksemel_init()
|
||
|
{
|
||
|
iks_set_mem_funcs(iksemel_alloc, iksemel_free);
|
||
|
}
|
||
|
|
||
|
/*=========================================================================*/
|
||
|
|
||
|
/* 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.
|
||
|
*/
|
||
|
{
|
||
|
char *xml_string;
|
||
|
iks *node;
|
||
|
vector_t *root;
|
||
|
|
||
|
/* get the root of the structure to be used */
|
||
|
root = sp->u.vec;
|
||
|
|
||
|
/* start generating the tree */
|
||
|
node = generate_xml_node(root, NULL);
|
||
|
|
||
|
/* At this point generate_xml_node() had
|
||
|
put an error handler on the stack.
|
||
|
*/
|
||
|
|
||
|
/* Clean up and return result */
|
||
|
free_svalue(sp);
|
||
|
|
||
|
/* get the xml string out of the stack */
|
||
|
memsafe(xml_string = iks_string(iks_stack(node), node)
|
||
|
, sizeof(*xml_string), "new xml string from node");
|
||
|
|
||
|
/* send the xml string back onto the stack */
|
||
|
put_c_string(sp, xml_string);
|
||
|
|
||
|
/* clean up, this will free the root node too, as it calls our error handler */
|
||
|
pop_stack();
|
||
|
|
||
|
return sp;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
int err;
|
||
|
|
||
|
memsafe(rec_data = xalloc(sizeof(*rec_data)), sizeof(*rec_data), "xml cleanup structure");
|
||
|
rec_data->node = NULL;
|
||
|
rec_data->parser = NULL;
|
||
|
push_error_handler(xml_cleanup, &(rec_data->head));
|
||
|
|
||
|
/* TODO: This can be implemented more efficient using the SAX interface. */
|
||
|
memsafe(rec_data->parser = iks_dom_new(&(rec_data->node)), 50, "new iksemel parser");
|
||
|
|
||
|
err = iks_parse(rec_data->parser, get_txt(sp->u.str), mstrsize(sp->u.str), 1);
|
||
|
switch (err)
|
||
|
{
|
||
|
case IKS_OK:
|
||
|
break;
|
||
|
|
||
|
case IKS_NOMEM:
|
||
|
errorf("Out of memory.\n");
|
||
|
|
||
|
/* NOTREACHED */
|
||
|
return sp;
|
||
|
|
||
|
case IKS_BADXML:
|
||
|
errorf("Bad arg 1 to xml_parse(): XML document not well formed (error "
|
||
|
"in line %ld, byte %ld).\n", iks_nr_lines(rec_data->parser)
|
||
|
, iks_nr_bytes(rec_data->parser));
|
||
|
|
||
|
/* NOTREACHED */
|
||
|
return sp;
|
||
|
|
||
|
case IKS_HOOK:
|
||
|
/* actually only used for a sax parser? */
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* we no longer need the string */
|
||
|
free_svalue(sp);
|
||
|
|
||
|
/* set 0 to always have a valid return */
|
||
|
put_number(sp, 0);
|
||
|
|
||
|
if (rec_data->node != NULL)
|
||
|
{
|
||
|
/* tree contains the tree now, this will put the resulting a */
|
||
|
parse_node(sp, rec_data->node);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* There was no XML tag or the tag was not closed properly. */
|
||
|
errorf("Bad arg 1 to xml_parse(): XML document not well formed (premature end "
|
||
|
"at line %ld, byte %ld).\n", iks_nr_lines(rec_data->parser)
|
||
|
, iks_nr_bytes(rec_data->parser));
|
||
|
}
|
||
|
|
||
|
/* At the end, be nice and remove the rest
|
||
|
using our error handler. */
|
||
|
pop_stack();
|
||
|
|
||
|
return sp;
|
||
|
}
|
||
|
|
||
|
#endif /* USE_IKSEMEL */
|