ldmud-3.3.719/
ldmud-3.3.719/doc/
ldmud-3.3.719/doc/efun.de/
ldmud-3.3.719/doc/efun/
ldmud-3.3.719/doc/man/
ldmud-3.3.719/doc/other/
ldmud-3.3.719/mud/
ldmud-3.3.719/mud/heaven7/
ldmud-3.3.719/mud/lp-245/
ldmud-3.3.719/mud/lp-245/banish/
ldmud-3.3.719/mud/lp-245/doc/
ldmud-3.3.719/mud/lp-245/doc/examples/
ldmud-3.3.719/mud/lp-245/doc/sefun/
ldmud-3.3.719/mud/lp-245/log/
ldmud-3.3.719/mud/lp-245/obj/Go/
ldmud-3.3.719/mud/lp-245/players/lars/
ldmud-3.3.719/mud/lp-245/room/death/
ldmud-3.3.719/mud/lp-245/room/maze1/
ldmud-3.3.719/mud/lp-245/room/sub/
ldmud-3.3.719/mud/lp-245/secure/
ldmud-3.3.719/mud/sticklib/
ldmud-3.3.719/mud/sticklib/src/
ldmud-3.3.719/mudlib/deprecated/
ldmud-3.3.719/mudlib/uni-crasher/
ldmud-3.3.719/pkg/
ldmud-3.3.719/pkg/debugger/
ldmud-3.3.719/pkg/diff/
ldmud-3.3.719/pkg/misc/
ldmud-3.3.719/src/
ldmud-3.3.719/src/autoconf/
ldmud-3.3.719/src/ptmalloc/
ldmud-3.3.719/src/util/
ldmud-3.3.719/src/util/erq/
ldmud-3.3.719/src/util/indent/hosts/next/
ldmud-3.3.719/src/util/xerq/
ldmud-3.3.719/src/util/xerq/lpc/
ldmud-3.3.719/src/util/xerq/lpc/www/
ldmud-3.3.719/test/generic/
ldmud-3.3.719/test/inc/
ldmud-3.3.719/test/t-0000398/
ldmud-3.3.719/test/t-0000548/
ldmud-3.3.719/test/t-030925/
ldmud-3.3.719/test/t-040413/
ldmud-3.3.719/test/t-041124/
ldmud-3.3.719/test/t-language/
/*------------------------------------------------------------------
 * 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 */