/*
 * XML Catalog Manager (xmlcatmgr)
 * $Id: xml.c,v 1.15 2004/05/02 07:56:13 jmmv Exp $
 *
 * Copyright (c) 2003, 2004 Julio M. Merino Vidal.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name of the author nor the names of contributors may
 *    be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * This file implements all XML mode actions and catalog handling
 * (although not parsing).
 */

#include "system.h"

#ifndef lint
__RCSID("$Id: xml.c,v 1.15 2004/05/02 07:56:13 jmmv Exp $");
#endif

#include "grstr.h"
#include "mem.h"
#include "linklist.h"
#include "xmldoc.h"
#include "xmlnode.h"
#include "xml.h"

/* This structure matches catalog entries with the number and name of
 * attributes they require.  A value of zero in the t_params fields
 * means that the specific entry cannot be given in the command line
 * as an argument to add or remove. */
static struct type {
    const char *t_name;
    int t_params;
    const char *t_origattr;
    const char *t_replattr;
} Types[] = {
    { "catalog",        0, NULL,                  NULL },
    { "delegatePublic", 2, "publicIdStartString", "catalog" },
    { "delegateSystem", 2, "systemIdStartString", "catalog" },
    { "delegateURI",    2, "uriStartString",      "catalog" },
    { "group",          0, NULL,                  NULL },
    { "nextCatalog",    1, "catalog",             NULL },
    { "public",         2, "publicId",            "uri" },
    { "rewriteSystem",  2, "systemIdStartString", "rewritePrefix" },
    { "rewriteURI",     2, "uriStartString",      "rewritePrefix" },
    { "system",         2, "systemId",            "uri" },
    { "uri",            2, "name",                "uri" },
    { NULL,             0, NULL,                  NULL }
};

static const struct type *get_type_info(const char *);
static bool add_entry(struct xmldoc *, const char *, const char *,
                      const char *, bool);
static bool remove_entry(struct xmldoc *, const char *, const char *);
static struct xmldoc *read_catalog(FILE *);
static bool write_catalog(struct xmldoc *, FILE *);
static bool validate_catalog(struct xmlnode *);
static struct xmlnode *search_entry(struct xmlnode *, const char *,
                                    const char *, bool);

/* --------------------------------------------------------------------- */

/*
 * The XML add action.  For each triplet of arguments, add_entry is
 * called to register the given entry in the catalog.
 */
bool
xml_add(int argc, char *const *argv, FILE *f, bool prepend)
{
    bool res;
    struct xmldoc *catalog;

    catalog = read_catalog(f);
    if (catalog == NULL)
        return false;

    res = true;
    while (argc > 0) {
        const char *type, *orig, *replace;

        if (argc == 1) {
            warnx("unbalanced arguments for `add' action");
            res = false;
            argc--; argv++;
        } else {
            type = argv[0];
            orig = argv[1];
            argc -= 2; argv += 2;

            if (argc >= 1) {
                replace = strcmp(argv[0], "--") != 0 ? argv[0] : NULL;
                argc--; argv++;
            } else
                replace = NULL;

            res &= add_entry(catalog, type, orig, replace, prepend);
        }
    }

    write_catalog(catalog, f);
    xmldoc_free(catalog);

    return res;
}

/* --------------------------------------------------------------------- */

/*
 * The XML create action.  Generates an empty catalog with a comment and
 * the root node in it.
 */
bool
xml_create(FILE *f)
{
    char buf[] = " Created by " PKGNAME "-" PKGVERSION " ";
    struct xmlattr *xa;
    struct xmldoc *catalog;
    struct xmlnode *xn;

    catalog = xmldoc_new("catalog PUBLIC \"-//OASIS//"
                         "DTD Entity Resolution XML Catalog V1.0//EN\"\n"
                         "    \"http://www.oasis-open.org/committees/"
                         "entity/release/1.0/catalog.dtd\"");

    xn = xmlnode_new(XMLNODE_TYPE_COMMENT, strdup("#COMMENT#"));
    xmlnode_set_text(xn, strdup(buf));
    xmldoc_append_node(catalog, xn);

    xn = xmlnode_new(XMLNODE_TYPE_ROOT, strdup("catalog"));
    xa = xmlattr_new(strdup("xmlns"),
                     strdup("urn:oasis:names:tc:entity:xmlns:xml:catalog"));
    XMLNODE_APPEND_ATTR(xn, xa);
    xmldoc_append_node(catalog, xn);

    xmldoc_write(catalog, f);
    xmldoc_free(catalog);

    return true;
}

/* --------------------------------------------------------------------- */

/*
 * The XML lookup action.  Searches the given entries in the catalog
 * file.  Only returns success if all of them were found.
 */
bool
xml_lookup(int argc, char *const *argv, FILE *f)
{
    bool res;
    struct xmldoc *catalog;

    assert(argc > 0 && argv != NULL && f != NULL);

    catalog = read_catalog(f);
    if (catalog == NULL)
        return false;

    res = true;
    while (argc > 0) {
        struct xmlnode *xn;

        xn = search_entry(XMLDOC_ROOT(catalog), NULL, argv[0], true);
        if (xn == NULL) {
            warnx("no matching entry for `%s'", argv[0]);
            res = false;
        }

        argc--; argv++;
    }

    xmldoc_free(catalog);

    return res;
}

/* --------------------------------------------------------------------- */

/*
 * The XML remove action.  Removes all given entries from the catalog.
 * Arguments are expected to come in pairs, although if only one is
 * provided, all matching catalog entries are removed (compatibility with
 * previous versions).
 */
bool
xml_remove(int argc, char *const *argv, FILE *f)
{
    bool res;
    struct xmldoc *catalog;

    assert(argv != NULL && f != NULL);

    catalog = read_catalog(f);
    if (catalog == NULL)
        return false;

    if (argc == 1) {
        warnx("enabling compatibility mode; removing ALL matching entries");
        res = remove_entry(catalog, NULL, argv[0]);
    } else {
        res = true;
        while (argc >= 2 && argc % 2 == 0) {
            res &= remove_entry(catalog, argv[0], argv[1]);
            argc -= 2; argv += 2;
        }

        if (argc % 2 != 0) {
            warnx("unbalanced arguments for `remove' action");
            res = false;
        }
    }

    write_catalog(catalog, f);
    xmldoc_free(catalog);

    return res;
}

/* --------------------------------------------------------------------- */

/*
 * Reads the given catalog file, and returns an 'xmldoc' object which
 * describes it.  If errors occur during parsing, returns NULL.
 */
static struct xmldoc *
read_catalog(FILE *f)
{
    struct xmldoc *catalog;

    catalog = xmldoc_parse(f);
    if (catalog == NULL) {
        warnx("errors while parsing catalog; aborting");
        catalog = NULL;
    } else if (!validate_catalog(XMLDOC_ROOT(catalog))) {
        warnx("catalog contains unknown tags or attributes; aborting");
        xmldoc_free(catalog);
        catalog = NULL;
    }

    return catalog;
}

/* --------------------------------------------------------------------- */

/*
 * Searches the given type in the 'Types' array, and returns a pointer
 * to it if found; otherwise returns NULL.
 */
static const struct type *
get_type_info(const char *type)
{
    bool found;
    int i;

    i = 0;
    found = false;
    while (!found && Types[i].t_name != NULL) {
        if (strcmp(Types[i].t_name, type) == 0)
            found = true;
        else
            i++;
    }

    if (!found)
        warnx("unknown entry type `%s'", type);

    return found ? &Types[i] : NULL;
}

/* --------------------------------------------------------------------- */

/*
 * Adds the given entry to the catalog file, if it's not already present.
 * Also checks if the type is valid.  'replace' may be null if the given
 * type only recognizes one argument.
 */
static bool
add_entry(struct xmldoc *c, const char *type, const char *orig,
          const char *replace, bool prepend)
{
    const struct type *tdata;
    struct xmlnode *xn;
    struct xmlattr *xa;

    assert(c != NULL && type != NULL && orig != NULL);

    tdata = get_type_info(type);
    if (tdata == NULL)
        return false;

    if (search_entry(XMLDOC_ROOT(c), type, orig, false) != NULL) {
        warnx("entry already exists for `%s' of type `%s'", orig, type);
        return false;
    }

    xn = xmlnode_new(XMLNODE_TYPE_ELEMENT, strdup(type));
    if (xn == NULL)
        return false;

    xa = xmlattr_new(strdup(tdata->t_origattr), strdup(orig));
    XMLNODE_APPEND_ATTR(xn, xa);

    if (replace != NULL) {
        xa = xmlattr_new(strdup(tdata->t_replattr), strdup(replace));
        XMLNODE_APPEND_ATTR(xn, xa);
    }

    if (prepend) {
        XMLNODE_PREPEND_CHILD(XMLDOC_ROOT(c), xn);
    } else {
        XMLNODE_APPEND_CHILD(XMLDOC_ROOT(c), xn);
    }

    return true;
}

/* --------------------------------------------------------------------- */

/*
 * Remove the given entry from the catalog file.  If 'type' is null,
 * removes all matching entries, to be compatible with previous versions.
 */
static bool
remove_entry(struct xmldoc *c, const char *type, const char *orig)
{
    bool found;
    struct xmlnode *xn;

    assert(c != NULL && orig != NULL);

    if (type != NULL) {
        const struct type *tdata;

        tdata = get_type_info(type);
        if (tdata == NULL)
            return false;
    }

    found = false;
    while ((xn = search_entry(XMLDOC_ROOT(c), type, orig, false)) != NULL) {
        XMLNODE_DETACH(xn);
        xmlnode_free(xn);
        found = true;
        if (type != NULL)
            break;
    }

    if (!found && type != NULL)
        warnx("no matching entry for `%s' of type `%s'", orig, type);
    else if (!found)
        warnx("no matching entry for `%s' of any type", orig);

    return found;
}

/* --------------------------------------------------------------------- */

/*
 * Recurse the given catalog (in fact, just its root node) and check, for
 * each element, that their attributes are valid (and are present).
 */
static bool
validate_catalog(struct xmlnode *xn)
{
    bool res;
    const struct type *tdata;

    assert(xn != NULL);

    res = true;

    tdata = get_type_info(XMLNODE_TAG(xn));
    if (tdata == NULL) {
        res = false;
    } else {
        struct xmlattr *aiter;
        struct xmlnode *niter;

        if (tdata->t_params > 0) {
            bool seenorig, seenrepl;

            seenorig = seenrepl = false;

            XMLNODE_FOREACH_ATTR(aiter, xn) {
                if (strcmp(XMLATTR_NAME(aiter), tdata->t_origattr) == 0)
                    seenorig = true;
                else if (strcmp(XMLATTR_NAME(aiter), tdata->t_replattr) == 0)
                    seenrepl = true;
                else if (!seenorig && !seenrepl) {
                    warnx("unknown attribute <%s>", XMLATTR_NAME(aiter));
                    res = false;
                }
            }

            if (!seenorig) {
                warnx("`%s' entry misses attribute `%s'", XMLNODE_TAG(xn),
                      tdata->t_origattr);
                res = false;
            }

            if (tdata->t_params > 1 && !seenrepl) {
                warnx("`%s' entry misses attribute `%s'", XMLNODE_TAG(xn),
                      tdata->t_replattr);
                res = false;
            }
        }

        XMLNODE_FOREACH_CHILD(niter, xn) {
            res &= validate_catalog(niter);
        }
    }

    return res;
}

/* --------------------------------------------------------------------- */

/*
 * Recurses the given node searching for an entry (described by its type
 * 'tname' and its contents 'name').  If 'tname' is null, only 'name' is
 * matched.  Returns a pointer to the matching node on success, or NULL
 * in case of failure.
 * If 'nostop' is true, the function will search for all matching nodes,
 * printing them to standard output.  The returned value will be a
 * pointer to the last matching node (useless).  This is only useful for
 * the lookup action.
 */
static struct xmlnode *
search_entry(struct xmlnode *xn, const char *tname, const char *name,
             bool nostop)
{
    const struct type *tdata;
    struct xmlnode *res;

    assert(xn != NULL && name != NULL);

    tdata = get_type_info(XMLNODE_TAG(xn));
    assert(tdata != NULL);

    res = NULL;

    if (tdata->t_params > 0 &&
        strcmp(xmlnode_get_attr_value(xn, tdata->t_origattr), name) == 0) {
        if ((tname == NULL) ||
            ((tname != NULL) && (strcmp(tdata->t_name, tname) == 0))) {
            res = xn;
        }
    } else {
        struct xmlnode *niter;

        XMLNODE_FOREACH_CHILD(niter, xn) {
            struct xmlnode *tmp;

            if ((tmp = search_entry(niter, tname, name, nostop)) != NULL) {
                res = tmp;
                if (nostop) {
                    struct xmlattr *aiter;

                    printf("%s", XMLNODE_TAG(res));
                    XMLNODE_FOREACH_ATTR(aiter, res) {
                        printf(" ");
                        xmlattr_write(aiter, stdout);
                    }
                    printf("\n");
                } else
                    break;
            }
        }
    }

    return res;
}

/* --------------------------------------------------------------------- */

/*
 * Write the given catalog to the file.  This truncates the stream to
 * zero bytes before writing anything, to ensure the file contains no
 * garbage.
 */
static bool
write_catalog(struct xmldoc *xd, FILE *f)
{
    assert(xd != NULL && f != NULL);

    rewind(f);
    fflush(f);
    ftruncate(fileno(f), 0);

    return xmldoc_write(xd, f);
}

/*
 * Local Variables: ***
 * mode: c ***
 * c-file-style: "stroustrup" ***
 * End: ***
 * vim: syntax=c:expandtab:shiftwidth=4:softtabstop=4
 */
