/*------------------------------------------------------------------ * 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 #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 array into an XML conform string, if * possible. The 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 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 */