/*
 * XDR (XML-Data Reduced) -> XSD (XML Schema Document) conversion
 *
 * Copyright 2010 Adam Martinson for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */


#include "config.h"

#include <assert.h>
#ifdef HAVE_LIBXML2
# include <libxml/tree.h>
#endif

#include "wine/debug.h"

/* Both XDR and XSD are valid XML
 * We just convert the doc tree, no need for a parser.
 */

#ifdef HAVE_LIBXML2

WINE_DEFAULT_DEBUG_CHANNEL(msxml);

static const xmlChar DT_prefix[] = "dt";
static const xmlChar DT_href[] = "urn:schemas-microsoft-com:datatypes";
static const xmlChar XDR_href[] = "urn:schemas-microsoft-com:xml-data";
static const xmlChar XSD_prefix[] = "xsd";
static const xmlChar XSD_href[] = "http://www.w3.org/2001/XMLSchema";

static const xmlChar xs_all[] = "all";
static const xmlChar xs_annotation[] = "annotation";
static const xmlChar xs_any[] = "any";
static const xmlChar xs_anyAttribute[] = "anyAttribute";
static const xmlChar xs_attribute[] = "attribute";
static const xmlChar xs_AttributeType[] = "AttributeType";
static const xmlChar xs_base[] = "base";
static const xmlChar xs_choice[] = "choice";
static const xmlChar xs_complexType[] = "complexType";
static const xmlChar xs_content[] = "content";
static const xmlChar xs_datatype[] = "datatype";
static const xmlChar xs_default[] = "default";
static const xmlChar xs_description[] = "description";
static const xmlChar xs_documentation[] = "documentation";
static const xmlChar xs_element[] = "element";
static const xmlChar xs_ElementType[] = "ElementType";
static const xmlChar xs_eltOnly[] = "eltOnly";
static const xmlChar xs_enumeration[] = "enumeration";
static const xmlChar xs_extension[] = "extension";
static const xmlChar xs_group[] = "group";
static const xmlChar xs_lax[] = "lax";
static const xmlChar xs_length[] = "length";
static const xmlChar xs_many[] = "many";
static const xmlChar xs_maxOccurs[] = "maxOccurs";
static const xmlChar xs_minOccurs[] = "minOccurs";
static const xmlChar xs_mixed[] = "mixed";
static const xmlChar xs_model[] = "model";
static const xmlChar xs_name[] = "name";
static const xmlChar xs_namespace[] = "namespace";
static const xmlChar xs_no[] = "no";
static const xmlChar xs_open[] = "open";
static const xmlChar xs_optional[] = "optional";
static const xmlChar xs_order[] = "order";
static const xmlChar xs_processContents[] = "processContents";
static const xmlChar xs_ref[] = "ref";
static const xmlChar xs_required[] = "required";
static const xmlChar xs_restriction[] = "restriction";
static const xmlChar xs_schema[] = "schema";
static const xmlChar xs_seq[] = "seq";
static const xmlChar xs_sequence[] = "sequence";
static const xmlChar xs_simpleContent[] = "simpleContent";
static const xmlChar xs_simpleType[] = "simpleType";
static const xmlChar xs_strict[] = "strict";
static const xmlChar xs_targetNamespace[] = "targetNamespace";
static const xmlChar xs_textOnly[] = "textOnly";
static const xmlChar xs_true[] = "true";
static const xmlChar xs_type[] = "type";
static const xmlChar xs_unbounded[] = "unbounded";
static const xmlChar xs_use[] = "use";
static const xmlChar xs_value[] = "value";
static const xmlChar xs_values[] = "values";
static const xmlChar xs_xsd_string[] = "xsd:string";

typedef enum _CONTENT_TYPE
{
    CONTENT_EMPTY,
    CONTENT_TEXTONLY,
    CONTENT_ELTONLY,
    CONTENT_MIXED
} CONTENT_TYPE;

typedef enum _ORDER_TYPE
{
    ORDER_SEQ,
    ORDER_MANY,
    ORDER_ONE
} ORDER_TYPE;

#define FOREACH_CHILD(node, child) \
    for (child = node->children; child != NULL; child = child->next) \
        if (child->type == XML_ELEMENT_NODE)

#define FOREACH_ATTR(node, attr) \
    for (attr = node->properties; attr != NULL; attr = attr->next)

#define FOREACH_NS(node, ns) \
    for (ns = node->nsDef; ns != NULL; ns = ns->next)

static inline xmlNodePtr get_schema(xmlNodePtr node)
{
    return xmlDocGetRootElement(node->doc);
}

static inline xmlNodePtr get_child(xmlNodePtr node, xmlChar const* name)
{
    xmlNodePtr child = NULL;
    if (node)
    {
        FOREACH_CHILD(node, child)
        {
            if (xmlStrEqual(child->name, name))
                break;
        }
    }

    return child;
}

static inline xmlNodePtr get_child_with_attr(xmlNodePtr node, xmlChar const* name,
                                             xmlChar const* attr_ns, xmlChar const* attr_name,
                                             xmlChar const* attr_val)
{
    xmlChar* str;
    if (node)
    {
        FOREACH_CHILD(node, node)
        {
            if (xmlStrEqual(node->name, name))
            {
                str = (attr_ns != NULL)? xmlGetNsProp(node, attr_name, attr_ns) :
                                                  xmlGetProp(node, attr_name);
                if (str)
                {
                    if (xmlStrEqual(str, attr_val))
                    {
                        xmlFree(str);
                        return node;
                    }
                    xmlFree(str);
                }
            }
        }
    }

    return NULL;
}

static inline xmlNsPtr get_dt_ns(xmlNodePtr node)
{
    xmlNsPtr ns;

    node = get_schema(node);
    assert(node != NULL);

    FOREACH_NS(node, ns)
    {
        if (xmlStrEqual(ns->href, DT_href))
            break;
    }

    return ns;
}

static inline xmlChar* get_dt_type(xmlNodePtr xdr)
{
    xmlChar* str = xmlGetNsProp(xdr, xs_type, DT_href);
    if (!str)
    {
        xmlNodePtr datatype = get_child(xdr, xs_datatype);
        if (datatype)
            str = xmlGetNsProp(datatype, xs_type, DT_href);
    }
    return str;
}

static inline xmlChar* get_attr_val(xmlAttrPtr attr)
{
    return xmlNodeGetContent((xmlNodePtr)attr);
}

static inline xmlNodePtr add_any_child(xmlNodePtr parent, BOOL set_occurs)
{
    xmlNodePtr child = xmlNewChild(parent, NULL, xs_any, NULL);
    if (set_occurs)
    {
        xmlSetProp(child, xs_minOccurs, BAD_CAST "0");
        xmlSetProp(child, xs_maxOccurs, xs_unbounded);
    }
    xmlSetProp(child, xs_processContents, xs_strict);
    return child;
}

static inline xmlNodePtr add_anyAttribute_child(xmlNodePtr parent)
{
    xmlNodePtr child = xmlNewChild(parent, NULL, xs_anyAttribute, NULL);
    xmlSetProp(child, xs_processContents, xs_lax);
    return child;
}

static inline xmlAttrPtr copy_prop_ignore_ns(xmlAttrPtr xdr_attr, xmlNodePtr node)
{
    xmlChar* str = get_attr_val(xdr_attr);
    xmlAttrPtr attr = xmlSetProp(node, xdr_attr->name, str);
    xmlFree(str);
    return attr;
}
static inline xmlAttrPtr XDR_A_default(xmlAttrPtr xdr_attr, xmlNodePtr node)
{
    TRACE("(%p, %p)\n", xdr_attr, node);

    return copy_prop_ignore_ns(xdr_attr, node);
}

static inline xmlAttrPtr XDR_A_dt_type(xmlAttrPtr xdr_attr, xmlNodePtr node)
{
    xmlChar* str = get_attr_val(xdr_attr);
    xmlAttrPtr attr;

    TRACE("(%p, %p)\n", xdr_attr, node);

    if (xmlStrEqual(str, xs_enumeration))
        attr = NULL;
    else
        attr = xmlSetNsProp(node, get_dt_ns(node), DT_prefix, str);
    xmlFree(str);
    return attr;
}

static xmlAttrPtr XDR_A_maxOccurs(xmlAttrPtr xdr_attr, xmlNodePtr node)
{
    xmlChar* str = get_attr_val(xdr_attr);
    xmlAttrPtr attr;

    TRACE("(%p, %p)\n", xdr_attr, node);

    if (xmlStrEqual(str, BAD_CAST "*"))
        attr = xmlSetProp(node, xs_maxOccurs, xs_unbounded);
    else
        attr = copy_prop_ignore_ns(xdr_attr, node);

    xmlFree(str);
    return attr;
}

static inline xmlAttrPtr XDR_A_minOccurs(xmlAttrPtr xdr_attr, xmlNodePtr node)
{
    TRACE("(%p, %p)\n", xdr_attr, node);

    return copy_prop_ignore_ns(xdr_attr, node);
}

static inline xmlAttrPtr XDR_A_name(xmlAttrPtr xdr_attr, xmlNodePtr node)
{
    TRACE("(%p, %p)\n", xdr_attr, node);

    return copy_prop_ignore_ns(xdr_attr, node);
}

static xmlAttrPtr XDR_A_type(xmlAttrPtr xdr_attr, xmlNodePtr node)
{
    xmlChar* str = get_attr_val(xdr_attr);
    xmlAttrPtr attr = xmlSetProp(node, xs_ref, str);

    TRACE("(%p, %p)\n", xdr_attr, node);

    xmlFree(str);
    return attr;
}

static xmlAttrPtr XDR_A_required(xmlAttrPtr xdr_attr, xmlNodePtr node)
{
    xmlChar* str = get_attr_val(xdr_attr);
    xmlAttrPtr attr;

    TRACE("(%p, %p)\n", xdr_attr, node);

    if (xmlStrEqual(str, xs_no))
        attr = xmlSetProp(node, xs_use, xs_optional);
    else /* yes */
        attr = xmlSetProp(node, xs_use, xs_required);
    xmlFree(str);
    return attr;
}

static xmlNodePtr XDR_E_description(xmlNodePtr xdr, xmlNodePtr parent)
{
    xmlNodePtr xsd_node = xmlNewChild(parent, NULL, xs_annotation, NULL);
    xmlAttrPtr xdr_attr;

    TRACE("(%p, %p)\n", xdr, parent);

    xmlNewChild(xsd_node, NULL, xs_documentation, xdr->content);

    FOREACH_ATTR(xdr, xdr_attr)
    {
        xmlCopyProp(xsd_node, xdr_attr);
    }
    return xsd_node;
}

static xmlNodePtr XDR_E_AttributeType(xmlNodePtr xdr, xmlNodePtr parent)
{
    xmlChar *str, *type = get_dt_type(xdr);
    xmlNodePtr xsd_node, xsd_child, xdr_child;
    xmlAttrPtr xdr_attr;

    TRACE("(%p, %p)\n", xdr, parent);

    xsd_node = xmlNewChild(parent, NULL, xs_attribute, NULL);

    if (type && xmlStrEqual(type, xs_enumeration))
    {
        xmlChar *tmp, *tokBegin, *tokEnd = NULL;
        xmlNodePtr xsd_enum;
        xsd_child = xmlNewChild(xsd_node, NULL, xs_simpleType, NULL);
        xsd_child = xmlNewChild(xsd_child, NULL, xs_restriction, NULL);
        xmlSetProp(xsd_child, xs_base, xs_xsd_string);

        tokBegin = str = xmlGetNsProp(xdr, xs_values, DT_href);
        while (tokBegin && *tokBegin)
        {
            while (*tokBegin && isspace(*tokBegin))
                ++tokBegin;
            tokEnd = tokBegin;
            while (*tokEnd && !isspace(*tokEnd))
                ++tokEnd;
            if (tokEnd == tokBegin)
                break;
            xsd_enum = xmlNewChild(xsd_child, NULL, xs_enumeration, NULL);
            tmp = xmlStrndup(tokBegin, tokEnd-tokBegin);
            xmlSetProp(xsd_enum, xs_value, tmp);
            xmlFree(tmp);
            tokBegin = tokEnd;
        }
        xmlFree(str);

    }
    else if (type)
    {
        str = xmlStrdup(DT_prefix);
        str = xmlStrcat(str, BAD_CAST ":");
        str = xmlStrcat(str, type);
        xmlSetProp(xsd_node, xs_type, str);
        xmlFree(str);
    }
    xmlFree(type);

    FOREACH_ATTR(xdr, xdr_attr)
    {
        if (xmlStrEqual(xdr_attr->name, xs_default))
            XDR_A_default(xdr_attr, xsd_node);
        else if (xmlStrEqual(xdr_attr->name, xs_name))
            XDR_A_name(xdr_attr, xsd_node);
        else if (xmlStrEqual(xdr_attr->name, xs_type) && xdr_attr->ns == get_dt_ns(xdr))
            XDR_A_dt_type(xdr_attr, xsd_node);
        else if (xmlStrEqual(xdr_attr->name, xs_values) && xdr_attr->ns == get_dt_ns(xdr))
            ; /* already handled */
        else if (xmlStrEqual(xdr_attr->name, xs_required))
            XDR_A_required(xdr_attr, xsd_node);
        else
            xmlCopyProp(xsd_node, xdr_attr);
    }

    FOREACH_CHILD(xdr, xdr_child)
    {
        if (xmlStrEqual(xdr_child->name, xs_datatype))
            ; /* already handled */
        else if (xmlStrEqual(xdr_child->name, xs_description))
            XDR_E_description(xdr_child, xsd_node);
        else
            FIXME("unexpected child <%s>\n", xdr_child->name);
    }

    return xsd_node;
}

static xmlNodePtr XDR_E_attribute(xmlNodePtr xdr, xmlNodePtr parent)
{
    xmlChar* str = xmlGetProp(xdr, xs_type);
    xmlNodePtr xsd_node, xdr_child, xdr_attrType;
    xmlAttrPtr xdr_attr;

    TRACE("(%p, %p)\n", xdr, parent);

    xdr_attrType = get_child_with_attr(xdr->parent, xs_AttributeType, NULL, xs_name, str);
    xmlFree(str);

    if (xdr_attrType)
        xsd_node = XDR_E_AttributeType(xdr_attrType, parent);
    else
        xsd_node = xmlNewChild(parent, NULL, xs_attribute, NULL);

    FOREACH_ATTR(xdr, xdr_attr)
    {
        if (xmlStrEqual(xdr_attr->name, xs_default))
            XDR_A_default(xdr_attr, xsd_node);
        else if (xmlStrEqual(xdr_attr->name, xs_type) && !xdr_attrType)
            XDR_A_type(xdr_attr, xsd_node);
        else if (xmlStrEqual(xdr_attr->name, xs_required))
            XDR_A_required(xdr_attr, xsd_node);
        else
            xmlCopyProp(xsd_node, xdr_attr);
    }

    FOREACH_CHILD(xdr, xdr_child)
    {
        FIXME("unexpected child <%s>\n", xdr_child->name);
    }

    return xsd_node;
}

static xmlNodePtr XDR_E_element(xmlNodePtr xdr, xmlNodePtr parent)
{
    xmlNodePtr xdr_child, xsd_node = xmlNewChild(parent, NULL, xs_element, NULL);
    xmlAttrPtr xdr_attr;

    FOREACH_ATTR(xdr, xdr_attr)
    {
        if (xmlStrEqual(xdr_attr->name, xs_type))
            XDR_A_type(xdr_attr, xsd_node);
        else if (xmlStrEqual(xdr_attr->name, xs_maxOccurs))
            XDR_A_maxOccurs(xdr_attr, xsd_node);
        else if (xmlStrEqual(xdr_attr->name, xs_minOccurs))
            XDR_A_minOccurs(xdr_attr, xsd_node);
        else
            xmlCopyProp(xsd_node, xdr_attr);
    }

    FOREACH_CHILD(xdr, xdr_child)
    {
        FIXME("unexpected child <%s>\n", xdr_child->name);
    }

    return xsd_node;
}

static xmlNodePtr XDR_E_group(xmlNodePtr xdr, xmlNodePtr parent)
{
    xmlNodePtr xdr_child, xsd_node;
    xmlChar* str = xmlGetProp(xdr, xs_order);
    xmlAttrPtr xdr_attr;

    TRACE("(%p, %p)\n", xdr, parent);

    if (!str || xmlStrEqual(str, xs_seq))
        xsd_node = xmlNewChild(parent, NULL, xs_sequence, NULL);
    else if (xmlStrEqual(str, xs_many))
        xsd_node = xmlNewChild(parent, NULL, xs_choice, NULL);
    else /* one */
        xsd_node = xmlNewChild(parent, NULL, xs_all, NULL);
    xmlFree(str);

    FOREACH_ATTR(xdr, xdr_attr)
    {
        if (xmlStrEqual(xdr_attr->name, xs_order))
            ; /* already handled */
        else if (xmlStrEqual(xdr_attr->name, xs_model))
            ; /* ignored */
        else if (xmlStrEqual(xdr_attr->name, xs_maxOccurs))
            XDR_A_maxOccurs(xdr_attr, xsd_node);
        else if (xmlStrEqual(xdr_attr->name, xs_minOccurs))
            XDR_A_minOccurs(xdr_attr, xsd_node);
        else
            xmlCopyProp(xsd_node, xdr_attr);
    }

    FOREACH_CHILD(xdr, xdr_child)
    {
        if (xmlStrEqual(xdr_child->name, xs_description))
            XDR_E_description(xdr_child, xsd_node);
        else if (xmlStrEqual(xdr_child->name, xs_element))
            XDR_E_element(xdr_child, xsd_node);
    }

    return xsd_node;
}

static xmlNodePtr XDR_E_ElementType(xmlNodePtr xdr, xmlNodePtr parent)
{
    xmlChar *str, *type = get_dt_type(xdr);
    BOOL is_open = TRUE;
    int n_attributes = 0, n_elements = 0, n_groups = 0;
    CONTENT_TYPE content;
    ORDER_TYPE order;
    xmlNodePtr xsd_node, xsd_type, xsd_child, xdr_child;
    xmlAttrPtr xdr_attr;
    xmlNsPtr dt_ns = get_dt_ns(parent);

    TRACE("(%p, %p)\n", xdr, parent);

    str = xmlGetProp(xdr, xs_model);
    if (str && !xmlStrEqual(str, xs_open))
        is_open = FALSE;
    xmlFree(str);

    if (type)
    {
        content = CONTENT_TEXTONLY;
    }
    else
    {
        str = xmlGetProp(xdr, xs_content);
        if (!str || xmlStrEqual(str, xs_mixed))
            content = CONTENT_MIXED;
        else if (xmlStrEqual(str, xs_eltOnly))
            content = CONTENT_ELTONLY;
        else if (xmlStrEqual(str, xs_textOnly))
            content = CONTENT_TEXTONLY;
        else /* empty */
            content = CONTENT_EMPTY;
        xmlFree(str);
    }

    str = xmlGetProp(xdr, xs_order);
    if (!str || xmlStrEqual(str, xs_seq))
    {
        order = ORDER_SEQ;
    }
    else if (xmlStrEqual(str, xs_many))
    {
        order = ORDER_MANY;
    }
    else /* one */
    {
        order = ORDER_ONE;
        is_open = FALSE;
    }
    xmlFree(str);

    FOREACH_CHILD(xdr, xdr_child)
    {
        if (xmlStrEqual(xdr_child->name, xs_element))
            ++n_elements;
        else if (xmlStrEqual(xdr_child->name, xs_group))
            ++n_groups;
        else if (xmlStrEqual(xdr_child->name, xs_attribute))
            ++n_attributes;
    }

    xsd_node = xmlNewChild(parent, NULL, xs_element, NULL);
    assert(xsd_node != NULL);
    switch (content)
    {
        case CONTENT_MIXED:
        case CONTENT_ELTONLY:
            {
                xmlNodePtr xsd_base;
                xsd_type = xmlNewChild(xsd_node, NULL, xs_complexType, NULL);

                if (content == CONTENT_MIXED)
                    xmlSetProp(xsd_type, xs_mixed, xs_true);

                if (is_open)
                    xsd_base = xmlNewChild(xsd_type, NULL, xs_sequence, NULL);
                else
                    xsd_base = xsd_type;

                if (is_open && n_elements < 2 && !n_groups)
                {/* no specific sequence of elements we need,
                    just has to start with the right one, if any */
                    if ((xdr_child = get_child(xdr, xs_element)))
                    {
                        xsd_child = XDR_E_element(xdr_child, xsd_base);
                        xmlUnsetProp(xsd_child, xs_maxOccurs);
                    }
                }
                else
                {
                    switch (order)
                    {
                        case ORDER_SEQ:
                            xsd_child = xmlNewChild(xsd_base, NULL, xs_sequence, NULL);
                            break;
                        case ORDER_MANY:
                            xsd_child = xmlNewChild(xsd_base, NULL, xs_choice, NULL);
                            xmlSetProp(xsd_child, xs_maxOccurs, xs_unbounded);
                            break;
                        case ORDER_ONE:
                            xsd_child = xmlNewChild(xsd_base, NULL, xs_all, NULL);
                            break;
                    }

                    FOREACH_CHILD(xdr, xdr_child)
                    {
                        if (xmlStrEqual(xdr_child->name, xs_element))
                            XDR_E_element(xdr_child, xsd_child);
                        else if (xmlStrEqual(xdr_child->name, xs_group))
                            XDR_E_group(xdr_child, xsd_child);
                    }
                }

                if (n_attributes)
                {
                    FOREACH_CHILD(xdr, xdr_child)
                    {
                        if (xmlStrEqual(xdr_child->name, xs_attribute))
                            XDR_E_attribute(xdr_child, xsd_type);
                    }
                }

                if (is_open)
                {
                    add_any_child(xsd_base, TRUE);
                    add_anyAttribute_child(xsd_type);
                }
            }
            break;
        case CONTENT_TEXTONLY:
            {
                if (is_open)
                {
                    xsd_type = xmlNewChild(xsd_node, NULL, xs_complexType, NULL);
                    if (type)
                    {
                        xsd_child = xmlNewChild(xsd_type, NULL, xs_simpleContent, NULL);
                        xsd_child = xmlNewChild(xsd_child, NULL, xs_extension, NULL);
                        str = xmlStrdup(DT_prefix);
                        str = xmlStrcat(str, BAD_CAST ":");
                        str = xmlStrcat(str, type);
                        xmlSetProp(xsd_child, xs_base, str);
                        xmlFree(str);
                        assert(dt_ns != NULL);
                        xmlSetNsProp(xsd_node, dt_ns, DT_prefix, type);
                    }
                    else
                    {
                        xmlSetProp(xsd_type, xs_mixed, xs_true);
                        xsd_child = xmlNewChild(xsd_type, NULL, xs_choice, NULL);
                        xmlSetProp(xsd_child, xs_minOccurs, BAD_CAST "0");
                        xmlSetProp(xsd_child, xs_maxOccurs, xs_unbounded);
                        xsd_child = add_any_child(xsd_child, FALSE);
                        xmlSetProp(xsd_child, xs_namespace, BAD_CAST "##other");
                        xsd_child = xsd_type;
                    }

                    if (n_attributes)
                        FOREACH_CHILD(xdr, xdr_child)
                        {
                            if (xmlStrEqual(xdr_child->name, xs_attribute))
                                XDR_E_attribute(xdr_child, xsd_child);
                        }

                    xmlNewChild(xsd_child, NULL, xs_anyAttribute, NULL);
                }
                else if (!n_attributes)
                {
                    if (type)
                    {
                        str = xmlStrdup(DT_prefix);
                        str = xmlStrcat(str, BAD_CAST ":");
                        str = xmlStrcat(str, type);
                        xmlSetProp(xsd_node, xs_type, str);
                        xmlFree(str);
                        str = NULL;
                        xmlSetNsProp(xsd_node, dt_ns, DT_prefix, type);
                    }
                    else
                    {
                        xmlSetProp(xsd_node, xs_type, xs_xsd_string);
                    }
                }
                else
                {
                    xsd_type = xmlNewChild(xsd_node, NULL, xs_complexType, NULL);
                    xsd_child = xmlNewChild(xsd_type, NULL, xs_simpleContent, NULL);
                    xsd_child = xmlNewChild(xsd_child, NULL, xs_extension, NULL);
                    xmlSetProp(xsd_child, xs_base, xs_xsd_string);

                    FOREACH_CHILD(xdr, xdr_child)
                    {
                        if (xmlStrEqual(xdr_child->name, xs_attribute))
                            XDR_E_attribute(xdr_child, xsd_child);
                    }
                }
            }
            break;
        case CONTENT_EMPTY: /* not allowed with model="open" */
            {
                if (n_attributes)
                {
                    xsd_type = xmlNewChild(xsd_node, NULL, xs_complexType, NULL);

                    FOREACH_CHILD(xdr, xdr_child)
                    {
                        if (xmlStrEqual(xdr_child->name, xs_attribute))
                            XDR_E_attribute(xdr_child, xsd_type);
                    }
                }
                else
                {
                    xsd_type = xmlNewChild(xsd_node, NULL, xs_simpleType, NULL);
                    xsd_child = xmlNewChild(xsd_type, NULL, xs_restriction, NULL);
                    xmlSetProp(xsd_child, xs_base, xs_xsd_string);
                    xsd_child = xmlNewChild(xsd_child, NULL, xs_length, NULL);
                    xmlSetProp(xsd_child, xs_value, BAD_CAST "0");
                }
            }
            break;
    }
    xmlFree(type);

    FOREACH_ATTR(xdr, xdr_attr)
    {
        if (xmlStrEqual(xdr_attr->name, xs_content))
            ; /* already handled */
        else if (xmlStrEqual(xdr_attr->name, xs_name))
            XDR_A_name(xdr_attr, xsd_node);
        else if (xmlStrEqual(xdr_attr->name, xs_type) && xdr_attr->ns == get_dt_ns(xdr))
            XDR_A_dt_type(xdr_attr, xsd_node);
        else if (xmlStrEqual(xdr_attr->name, xs_model))
            ; /* already handled */
        else if (xmlStrEqual(xdr_attr->name, xs_order))
            ; /* already handled */
        else
            xmlCopyProp(xsd_node, xdr_attr);

    }

    FOREACH_CHILD(xdr, xdr_child)
    {
        if (xmlStrEqual(xdr_child->name, xs_attribute))
            ; /* already handled */
        else if (xmlStrEqual(xdr_child->name, xs_AttributeType))
            ; /* handled through XDR_E_attribute when parent is not <Schema> */
        else if (xmlStrEqual(xdr_child->name, xs_datatype))
            ; /* already handled */
        else if (xmlStrEqual(xdr_child->name, xs_description))
            XDR_E_description(xdr_child, xsd_node);
        else if (xmlStrEqual(xdr_child->name, xs_element))
            ; /* already handled */
        else if (xmlStrEqual(xdr_child->name, xs_group))
            ; /* already handled */
        else
            FIXME("unexpected child <%s>\n", xdr_child->name);
    }

    return xsd_node;
}

static xmlNodePtr XDR_E_Schema(xmlNodePtr xdr, xmlNodePtr parent, xmlChar const* nsURI)
{
    xmlNodePtr xsd_node, xdr_child;
    xmlNsPtr ns, xdr_ns;
    xmlAttrPtr xdr_attr;

    TRACE("(%p, %p)\n", xdr, parent);

    xsd_node = xmlNewDocNode((xmlDocPtr)parent, NULL, xs_schema, NULL);
    xmlDocSetRootElement((xmlDocPtr)parent, xsd_node);
    assert(xsd_node != NULL);

    if (nsURI && *nsURI) xmlNewNs(xsd_node, nsURI, NULL);
    ns = xmlNewNs(xsd_node, XSD_href, XSD_prefix);
    assert(ns != NULL);

    xmlSetNs(xsd_node, ns);

    if (nsURI && *nsURI) xmlSetProp(xsd_node, xs_targetNamespace, nsURI);

    FOREACH_NS(xdr, xdr_ns)
    {
        /* TODO: special handling for dt namespace? */
        assert(xdr_ns->href != NULL);
        if (xmlStrEqual(xdr_ns->href, XDR_href))
            ; /* ignored */
        else if (xdr_ns->prefix != NULL)
            xmlNewNs(xsd_node, xdr_ns->href, xdr_ns->prefix);
        else
            FIXME("unexpected default xmlns: %s\n", xdr_ns->href);
    }

    FOREACH_ATTR(xdr, xdr_attr)
    {
        xmlCopyProp(xsd_node, xdr_attr);
    }

    FOREACH_CHILD(xdr, xdr_child)
    {
        if (xmlStrEqual(xdr_child->name, xs_AttributeType))
            XDR_E_AttributeType(xdr_child, xsd_node);
        else if (xmlStrEqual(xdr_child->name, xs_description))
            XDR_E_description(xdr_child, xsd_node);
        else if (xmlStrEqual(xdr_child->name, xs_ElementType))
            XDR_E_ElementType(xdr_child, xsd_node);
        else
            FIXME("unexpected child <%s>\n", xdr_child->name);
    }

    return xsd_node;
}

xmlDocPtr XDR_to_XSD_doc(xmlDocPtr xdr_doc, xmlChar const* nsURI)
{
    xmlDocPtr xsd_doc = xmlNewDoc(NULL);

    TRACE("(%p)\n", xdr_doc);

    XDR_E_Schema(get_schema((xmlNodePtr)xdr_doc), (xmlNodePtr)xsd_doc, nsURI);

    return xsd_doc;
}

#endif /* HAVE_LIBXML2 */
