/* Firebox Tools - Outils de configurations pour le WM Firebox
 * xml.c - lecture ou écriture de documents XML
 *
 * Copyright (C) 2006-2007 Cyrille Bagard
 *
 *  This file is part of Firebox Tools.
 *
 *  Firebox Tools is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  Firebox Tools 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


#include "xml.h"


#include <stdarg.h>
#include <string.h>


#include "extstr.h"


#ifdef DEBUG
#   define XML_LOG fprintf
#else
#   define XML_LOG if (FALSE) fprintf
#endif



/******************************************************************************
*                                                                             *
*  Paramètres  : filename = nom du fichier à ouvrir.                          *
*                xdoc     = structure XML chargée. [OUT]                      *
*                context  = contexte à utiliser pour les recherches. [OUT]    *
*                                                                             *
*  Description : Crée un nouveau fichier XML.                                 *
*                                                                             *
*  Retour      : true si l'opération a pu s'effectuer, false sinon.           *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool create_new_xml_file(xmlDocPtr *xdoc, xmlXPathContextPtr *context)
{
    *xdoc = xmlNewDoc(BAD_CAST "1.0");

    if (*xdoc == NULL)
        return false;

    *context = xmlXPathNewContext(*xdoc);

    if (*context == NULL)
    {
        xmlFreeDoc(*xdoc);
        return false;
    }

    return true;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : xdoc     = structure XML chargée.                            *
*                filename = nom du fichier à remplir.                         *
*                                                                             *
*  Description : Sauvegarde une structure XML dans un fichier.                *
*                                                                             *
*  Retour      : true si l'opération a pu s'effectuer, false sinon.           *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool save_xml_file(xmlDocPtr xdoc, const char *filename)
{
    int ret;                                /* Bilan de l'appel            */

    ret = xmlSaveFormatFileEnc(filename, xdoc, "UTF-8", 1);

    return (ret != -1);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : xdoc     = structure XML chargée à supprimer.                *
*                context  = contexte utilisé pour les recherches.             *
*                                                                             *
*  Description : Ferme une structure XML.                                     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void close_xml_file(xmlDocPtr xdoc, xmlXPathContextPtr context)
{
    xmlXPathFreeContext(context);
    xmlFreeDoc(xdoc);

    xmlCleanupParser();

}



/* ---------------------------------------------------------------------------------- */
/*                       OPERATIONS DE LECTURE D'UN FICHIER XML                       */
/* ---------------------------------------------------------------------------------- */


/******************************************************************************
*                                                                             *
*  Paramètres  : filename = nom du fichier à ouvrir.                          *
*                xdoc     = structure XML chargée. [OUT]                      *
*                xpathCtx = contexte à utiliser pour les recherches. [OUT]    *
*                                                                             *
*  Description : Ouvre un fichier XML de façon encadrée.                      *
*                                                                             *
*  Retour      : true si l'opération a pu s'effectuer, false sinon.           *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

gboolean open_xml_file(const char *filename, xmlDoc **xdoc, xmlXPathContextPtr *xpathCtx)
{
    *xdoc = xmlParseFile(filename);

    if (*xdoc == NULL)
    {
        XML_LOG(stderr, "Can not parse the XML file '%s'\n", filename);
        return FALSE;
    }

    *xpathCtx = xmlXPathNewContext(*xdoc);

    if (*xpathCtx == NULL)
    {
        XML_LOG(stderr, "Unable to create new XPath context\n");
        xmlFreeDoc(*xdoc);
        return FALSE;
    }

    return TRUE;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : xpathCtx = contexte à utiliser pour les recherches.          *
*                path     = chemin d'accès au noeud visé.                     *
*                                                                             *
*  Description : Obtient de façon encadrée l'accès à un noeud défini.         *
*                                                                             *
*  Retour      : Adresse de l'accès trouvé.                                   *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

xmlXPathObjectPtr get_node_xpath_object(xmlXPathContextPtr xpathCtx, const char *path)
{
    xmlXPathObjectPtr result;               /* Noeud XML à renvoyer        */

    result = xmlXPathEvalExpression(BAD_CAST path, xpathCtx);

    if (result == NULL)
    {
        XML_LOG(stderr, "Unable to evaluate xpath expression '%s'\n", path);
        return NULL;
    }

    if (result->nodesetval == NULL)
    {
        XML_LOG(stderr, "Node '%s' not found\n", path);
        xmlXPathFreeObject(result);
        return NULL;
    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : node = noeud dont une propriété est à lire.                  *
*                                                                             *
*  Description : Obtient une valeur placée entre <...> et </...>.             *
*                                                                             *
*  Retour      : Valeur sous forme de chaîne de caractères ou NULL.           *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

char *qck_get_node_text_value(xmlNodePtr node)
{
    char *result;                           /* Valeur en question renvoyée */

    result = NULL;

    if (node != NULL)
        if (node->children != NULL)
            if (node->children->content != NULL)
                result = strdup((char *)node->children->content);

    if (result == NULL) XML_LOG(stderr, "No text value for node '%s'\n", node->name);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : xpathCtx = contexte à utiliser pour les recherches.          *
*                path     = chemin d'accès au noeud visé.                     *
*                                                                             *
*  Description : Obtient une valeur placée entre <...> et </...>.             *
*                                                                             *
*  Retour      : Valeur sous forme de chaîne de caractères ou NULL.           *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

char *get_node_text_value(xmlXPathContextPtr xpathCtx, const char *path)
{
    char *result;                           /* Valeur en question renvoyée */
    xmlXPathObjectPtr xpathObj;             /* Point de départ XML         */

    result = NULL;

    xpathObj = get_node_xpath_object(xpathCtx, path);
    if (xpathObj == NULL) return NULL;

    if (xpathObj->nodesetval->nodeNr > 0)
        result = qck_get_node_text_value(xpathObj->nodesetval->nodeTab[0]);

    xmlXPathFreeObject(xpathObj);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : node = noeud dont une propriété est à lire.                  *
*                name = nom de la propriété à lire.                           *
*                                                                             *
*  Description : Obtient la valeur d'une propriété d'un élément.              *
*                                                                             *
*  Retour      : Valeur sous forme de chaîne de caractères ou NULL.           *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

char *qck_get_node_prop_value(xmlNodePtr node, const char *name)
{
    char *result;                           /* Valeur en question renvoyée */
    xmlAttrPtr attrib;                      /* Liste d'attributs présents  */

    result = NULL;

    if (node == NULL) return NULL;

    /* Lecture de la valeur */

    for (attrib = node->properties; attrib != NULL && result == NULL; attrib = attrib->next)
        if (xmlStrEqual(attrib->name, BAD_CAST name)) result = strdup((char *)attrib->children->content);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : xpathCtx = contexte à utiliser pour les recherches.          *
*                path     = chemin d'accès au noeud à traiter.                *
*                name     = nom de la propriété à lire.                       *
*                                                                             *
*  Description : Obtient la valeur d'une propriété d'un élément.              *
*                                                                             *
*  Retour      : Valeur sous forme de chaîne de caractères ou NULL.           *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

char *get_node_prop_value(xmlXPathContextPtr xpathCtx, const char *path, const char *name)
{
    char *result;                           /* Valeur en question renvoyée */
    xmlXPathObjectPtr xpathObj;             /* Point de départ XML         */

    result = NULL;

    xpathObj = get_node_xpath_object(xpathCtx, path);
    if (xpathObj == NULL) return NULL;

    if (xpathObj->nodesetval->nodeNr > 0)
        result = qck_get_node_prop_value(xpathObj->nodesetval->nodeTab[0], name);

    xmlXPathFreeObject(xpathObj);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : node = noeud de texte avec un lien avec le document XML.     *
*                                                                             *
*  Description : Construit un chemin d'accès complet selon le fichier XML.    *
*                                                                             *
*  Retour      : Valeur à libérer de la mémoire après usage ou NULL.          *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

char *qck_build_filename_with_doc_url(xmlNodePtr node)
{
    char *result;                           /* Construction à retourner    */
    char *text;                             /* Valeur du texte lu          */
    char *last;                             /* Point de remplacement       */

    result = NULL;

    text = qck_get_node_text_value(node);

    if (text != NULL)
    {
        result = (char *)calloc(xmlStrlen(node->doc->URL) + strlen(text) + 1, sizeof(char));

        strcpy(result, (const char *)node->doc->URL);

        last = strrchr(result, '/');
        last++;

        strcpy(last, text);
        free(text);

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : xpathCtx = contexte à utiliser pour les recherches.          *
*                path     = chemin d'accès au noeud à traiter.                *
*                                                                             *
*  Description : Construit un chemin d'accès complet selon le fichier XML.    *
*                                                                             *
*  Retour      : Valeur sous forme de chaîne de caractères ou NULL.           *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

char *build_filename_with_doc_url(xmlXPathContextPtr xpathCtx, const char *path)
{
    char *result;                           /* Valeur en question renvoyée */
    xmlXPathObjectPtr xpathObj;             /* Point de départ XML         */

    result = NULL;

    xpathObj = get_node_xpath_object(xpathCtx, path);
    if (xpathObj == NULL) return NULL;

    if (xpathObj->nodesetval->nodeNr > 0)
        result = qck_build_filename_with_doc_url(xpathObj->nodesetval->nodeTab[0]);

    xmlXPathFreeObject(xpathObj);

    return result;

}



/* ---------------------------------------------------------------------------------- */
/*                       OPERATIONS D'ECRITURE D'UN FICHIER XML                       */
/* ---------------------------------------------------------------------------------- */


/******************************************************************************
*                                                                             *
*  Paramètres  : filename = nom du fichier à ouvrir.                          *
*                                                                             *
*  Description : Amorce l'écriture d'un nouveau fichier XML.                  *
*                                                                             *
*  Retour      : Rédacteur mis en place ou NULL en cas d'erreur.              *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

xmlTextWriterPtr start_writing_xml_file(const char *filename)
{
    xmlTextWriterPtr result;                /* Moyen à retourner           */
    int retval;                             /* Bilan d'une opération       */

    result = xmlNewTextWriterFilename(filename, 0);

    if (result == NULL)
    {
        XML_LOG(stderr, "Error creating the xml writer\n");
        return NULL;
    }

    retval = xmlTextWriterStartDocument(result, NULL, "UTF-8", "yes");
    if (retval < 0)
    {
        XML_LOG(stderr, "Error at xmlTextWriterStartDocument\n");
        xmlFreeTextWriter(result);
        return NULL;
    }

    retval = xmlTextWriterSetIndent(result, 1);
    if (retval < 0)
    {
        XML_LOG(stderr, "Error setting indentation\n");
        xmlFreeTextWriter(result);
        return NULL;
    }

    retval = xmlTextWriterSetIndentString(result, BAD_CAST "\t");
    if (retval < 0)
    {
        XML_LOG(stderr, "Error setting indentation string\n");
        xmlFreeTextWriter(result);
        return NULL;
    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : writer = rédacteur dédié à l'écriture.                       *
*                                                                             *
*  Description : Met fin à l'écriture d'un nouveau fichier XML.               *
*                                                                             *
*  Retour      : Bilan de l'opération : true ou false.                        *
*                                                                             *
*  Remarques   : Ferme au besoin toutes les balises encore ouvertes.          *
*                                                                             *
******************************************************************************/

bool end_writing_xml_file(xmlTextWriterPtr writer)
{
    int retval;                             /* Bilan de l'opération        */

    retval = xmlTextWriterEndDocument(writer);
    if (retval < 0)
    {
        XML_LOG(stderr, "Error at xmlTextWriterEndDocument\n");
        return false;
    }

    xmlFreeTextWriter(writer);

    return true;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : writer = rédacteur dédié à l'écriture.                       *
*                name   = nom de la balise à écrire.                          *
*                                                                             *
*  Description : Ecrit une balise et ne la referme pas.                       *
*                                                                             *
*  Retour      : Bilan de l'opération : true ou false.                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool open_xml_element(xmlTextWriterPtr writer, const char *name)
{
    int retval;                             /* Bilan de l'opération        */

    retval = xmlTextWriterStartElement(writer, BAD_CAST name);

    if (retval < 0)
        XML_LOG(stderr, "Error at xmlTextWriterWriteFormatElement\n");

    return (retval >= 0);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : writer = rédacteur dédié à l'écriture.                       *
*                                                                             *
*  Description : Ferme la dernière balise laissée ouverte.                    *
*                                                                             *
*  Retour      : Bilan de l'opération : true ou false.                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool close_xml_element(xmlTextWriterPtr writer)
{
    int retval;                             /* Bilan de l'opération        */

    retval = xmlTextWriterEndElement(writer);

    if (retval < 0)
        XML_LOG(stderr, "Error at xmlTextWriterWriteFormatElement\n");

    return (retval >= 0);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : writer = rédacteur dédié à l'écriture.                       *
*                name   = nom de la balise à écrire.                          *
*                format = format de la chaîne à traiter.                      *
*                ...    = informations à inscrire.                            *
*                                                                             *
*  Description : Ecrit une balise avec un contenu textuel.                    *
*                                                                             *
*  Retour      : Bilan de l'opération : true ou false.                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool write_xml_element_with_content(xmlTextWriterPtr writer, const char *name, const char *format, ...)
{
    va_list ap;                             /* Liste d'arguments variable  */
    int retval;                             /* Bilan de l'opération        */

    va_start(ap, format);

    retval = xmlTextWriterWriteVFormatElement(writer, BAD_CAST name, format, ap);

    if (retval < 0)
        XML_LOG(stderr, "Error at xmlTextWriterWriteFormatElement\n");

    va_end(ap);

    return (retval >= 0);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : writer = rédacteur dédié à l'écriture.                       *
*                name   = nom de l'attribut à écrire.                         *
*                format = format de la chaîne à traiter.                      *
*                ...    = informations à inscrire.                            *
*                                                                             *
*  Description : Ecrit un attribut avec un contenu textuel.                   *
*                                                                             *
*  Retour      : Bilan de l'opération : true ou false.                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool write_xml_attribute(xmlTextWriterPtr writer, const char *name, const char *format, ...)
{
    va_list ap;                             /* Liste d'arguments variable  */
    int retval;                             /* Bilan de l'opération        */

    va_start(ap, format);

    retval = xmlTextWriterWriteVFormatAttribute(writer, BAD_CAST name, format, ap);

    if (retval < 0)
        XML_LOG(stderr, "Error at xmlTextWriterWriteFormatElement\n");

    va_end(ap);

    return (retval >= 0);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : writer = rédacteur dédié à l'écriture.                       *
*                format = format de la chaîne à traiter.                      *
*                ...    = informations à inscrire.                            *
*                                                                             *
*  Description : Ecrit un contenu textuel.                                    *
*                                                                             *
*  Retour      : Bilan de l'opération : true ou false.                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool write_xml_content(xmlTextWriterPtr writer, const char *format, ...)
{
    va_list ap;                             /* Liste d'arguments variable  */
    int retval;                             /* Bilan de l'opération        */

    va_start(ap, format);

    retval = xmlTextWriterWriteVFormatString(writer, format, ap);

    if (retval < 0)
        XML_LOG(stderr, "Error at xmlTextWriterWriteFormatElement\n");

    va_end(ap);

    return (retval >= 0);

}



/* ---------------------------------------------------------------------------------- */
/*                       OPERATIONS D'ECRITURE D'UN FICHIER XML                       */
/* ---------------------------------------------------------------------------------- */



/******************************************************************************
*                                                                             *
*  Paramètres  : context = contexte à utiliser pour les recherches.           *
*                path    = chemin d'accès au noeud visé.                      *
*                                                                             *
*  Description : Fournit le premier noeud correspondant à un chemin XPath.    *
*                                                                             *
*  Retour      : Adresse du noeud trouvé ou NULL en cas d'échec.              *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

xmlNodePtr get_node_from_xpath(xmlXPathContextPtr context, const char *path)
{
    xmlNodePtr result;                      /* Noeud trouvé à renvoyer     */
    xmlXPathObjectPtr xobject;              /* Point de départ XML         */

    result = NULL;

    xobject = get_node_xpath_object(context, path);
    if (xobject == NULL) return NULL;

    if (xobject->nodesetval->nodeNr > 0)
        result = xobject->nodesetval->nodeTab[0];

    xmlXPathFreeObject(xobject);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : xdoc    = structure XML chargée.                             *
*                context = contexte à utiliser pour les recherches.           *
*                path    = chemin d'accès au noeud visé.                      *
*                                                                             *
*  Description : S'assure qu'un noeud donné est bien présent dans le document.*
*                                                                             *
*  Retour      : Noeud en question ou NULL en cas d'échec à la création.      *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

xmlNodePtr ensure_node_exist(xmlDocPtr xdoc, xmlXPathContextPtr context, const char *path)
{
    xmlNodePtr result;                      /* Noeud à retourner           */
    char **levels;                          /* Niveaux dans le chemin      */
    size_t levels_count;                    /* Nombre de ces niveaux       */
    xmlNodePtr last;                        /* Dernier noeud valide        */
    size_t i;                               /* Boucle de parcours #1       */
    char *iter_path;                        /* Chamin d'accès pour le test */
    size_t j;                               /* Boucle de parcours #2       */
    xmlNodePtr iter;                        /* Test d'accès à un noeud     */
    char *cond;                             /* Marque de condition ('[')   */

    result = get_node_from_xpath(context, path);

    if (result == NULL)
    {
        levels = strtoka(path, "/", &levels_count);

        /* Recherche la racine valide la plus haute */

        last = xmlDocGetRootElement(xdoc);

        for (i = 0; i < levels_count && last != NULL; i++)
        {
            iter_path = strdup("");

            for (j = 0; j <= i; j++)
            {
                iter_path = stradd(iter_path, "/");
                iter_path = stradd(iter_path, levels[j]);
            }

            iter = get_node_from_xpath(context, iter_path);

            if (iter == NULL) break;
            else last = iter;

        }

        /* Inscription des noeuds restants */

        if (last == NULL)
        {
            last = xmlNewDocNode(xdoc, NULL, BAD_CAST levels[i++], NULL);
            xmlDocSetRootElement(xdoc, last);

            if (i == levels_count)
                result = last;

        }

        for ( ; i < levels_count && last != NULL; i++)
        {
            cond = strchr(levels[i], '[');
            if (cond != NULL) *cond = '\0';

            result = xmlNewDocNode(xdoc, NULL, BAD_CAST levels[i], NULL);
            result = xmlAddChild(last, result);
            last = result;
        }

        /* Libération de la mémoire */

        for (i = 0; i < levels_count; i++)
            free(levels[i]);

        free(levels);

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : xdoc    = structure XML chargée.                             *
*                context = contexte à utiliser pour les recherches.           *
*                path    = chemin d'accès au noeud visé.                      *
*                content = texte à inscrire en contenu.                       *
*                                                                             *
*  Description : S'assure qu'un noeud donné est bien présent dans le document.*
*                                                                             *
*  Retour      : true en cas de succès, false sinon.                          *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool add_content_to_node(xmlDocPtr xdoc, xmlXPathContextPtr context, const char *path, const char *content)
{
    xmlNodePtr node;                        /* Noeud à modifier            */

    if (content == NULL) return true;

    node = ensure_node_exist(xdoc, context, path);
    if (node == NULL) return false;

    xmlNodeSetContent(node, content);

    return true;

}