/*
Silky - A GTK+ client for SILC.
Copyright (C) 2003, 2004, 2005 Toni Willberg
- API for reading/writing Silky config file
This program 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.
This program 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
http://silky.sourceforge.net/
*/
#include <glib.h>
#include <glib/gprintf.h>
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "ui_console.h"
#include "common.h"
#include "xmlconfig.h"
#include "xmllayout.h"
#include "common.h"
#include "log.h"
xmlDocPtr config_main;
xmlDocPtr config_servers;
xmlDocPtr config_buddies;
extern SilkyXMLAttribute xml_attributes[];
extern SilkyXMLElement xml_elements[NUM_CONFIG][NUM_CM];
extern gint xml_attr_bits[];
xmlNodePtr xml_create_node(gint config_type, gint node_cn) {
xmlNodePtr parent = NULL, node;
xmlDocPtr doc = xml_doc(config_type);
debug("creating node %s", xml_elements[config_type][node_cn].name);
if( !doc ) {
debug("no document, aborting.");
return NULL;
}
if ( (config_type == CONFIG_MAIN) && (node = xml_get_node(config_type, doc, node_cn)) ) {
debug("working with main config and node %s already exists - ignoring", xml_elements[config_type][node_cn].name);
return node;
}
if( xml_elements[config_type][node_cn].parent ) parent = xml_create_node(config_type, xml_elements[config_type][node_cn].parent);
if( parent ) {
debug("creating node with parent");
return xmlNewTextChild(parent, NULL, xml_elements[config_type][node_cn].name, "");
} else {
debug("creating parentless node");
if( node_cn != CM_ROOT ) {
debug(">> WARNING: this is not desired root node!");
return NULL;
}
}
debug("creating ROOT element, behold!");
xmlDocSetRootElement( doc, (node = xmlNewNode(NULL, xml_elements[config_type][node_cn].name)) );
return node;
}
/* we also need to pass xmlDocPtr to this function, because we will be calling
it also when xml_doc(config_type) would return NULL */
/* auxiliary function for getting first node from xpath in document doc */
xmlNodePtr xml_get_node(gint config_type, xmlDocPtr doc, gint node_cn) {
xmlXPathObjectPtr result;
xmlXPathContextPtr context;
xmlNodePtr node;
gint cn = node_cn;
gchar *xpath = "";
if( !doc ) {
debug("doc == NULL, returning");
return NULL;
}
/* sanity check */
if( node_cn < 1 || node_cn >= NUM_CM ) {
debug("error - node_cn %d is not within limits!", node_cn);
return NULL;
}
cn = node_cn;
while( xml_elements[config_type][cn].name ) {
if( g_utf8_strlen(xpath, -1) )
xpath = g_strjoin("/", xml_elements[config_type][cn].name, xpath, NULL);
else xpath = xml_elements[config_type][cn].name;
cn = xml_elements[config_type][cn].parent;
}
xpath = g_strconcat("/", xpath, NULL);
context = xmlXPathNewContext(doc);
if( !(result= xmlXPathEvalExpression(xpath, context)) ) {
debug("No result found for %s", xpath);
return NULL;
}
if( xmlXPathNodeSetIsEmpty(result->nodesetval) ) {
return NULL;
}
xmlXPathFreeContext(context);
node = result->nodesetval->nodeTab[0];
xmlXPathFreeObject(result);
return node;
}
/* this will look up Silky's internal node number by xmlNode. we check all
parents of a given node, so duplicate node names in different places in xml
are supported. */
gint xml_get_node_cX_by_node(gint config_type, xmlNodePtr node) {
gint i, j, wrong = 0;
xmlNodePtr n;
for( i = 1; i < NUM_CM; i++ ) {
if( !xmlStrcmp(xml_elements[config_type][i].name, node->name) ) {
wrong = 0;
n = node;
j = i;
while( (n = n->parent) && (j = xml_elements[config_type][j].parent) ) {
if( xmlStrcmp(xml_elements[config_type][j].name, n->name) ) wrong++;
}
if( !wrong ) return i;
}
}
return 0;
}
/* this will return 0 if config is not read succesfully */
xmlDocPtr xml_read_config(gint config_type, xmlChar *filename) {
xmlNodePtr cur;
xmlDocPtr doc;
debug("Parsing config file %s", filename);
if( !(doc = xmlParseFile(filename)) ) {
debug("Cannot parse %s", filename);
return NULL;
}
if( !(cur = xmlDocGetRootElement(doc)) || xmlStrcmp(cur->name, (const xmlChar *) xml_elements[config_type][CM_ROOT].name) ) {
debug("Correct root element not found in %s", filename);
xmlFreeDoc(doc);
return NULL;
}
return doc;
}
/* this saves chosen xmlDoc to chosen filename. it tries to fopen()
filename for writing to decide if we're able to write to the file */
gint xml_save_config(gint config_type, xmlChar *filename) {
xmlNodePtr node;
FILE *f;
if( !(f = fopen(filename, "w")) ) {
debug("Cannot open file %s for writing!", filename);
debug("FIXME: create an errordialog in gui");
return 0;
}
fclose(f);
/* let's set correct version attributes for our root node */
node = xml_get_node(config_type, xml_doc(config_type), CM_ROOT);
xmlSetProp(node, xml_attributes[ATTRN_VERSION].name, SILKY_VERSION);
xmlSetProp(node, xml_attributes[ATTRN_CONFIG_VERSION].name, SILKY_CONFIG_VERSION);
xml_write_config(config_type, filename);
return 1;
}
xmlDocPtr xml_create_config(gint config_type) {
xmlDocPtr doc;
xmlNodePtr node;
gint i, a;
if( !(doc = xmlNewDoc("1.0")) ) {
debug("Could not create xmlDoc!");
return NULL;
}
if( config_type == CONFIG_MAIN ) {
/* this is special case, we need all nodes present */
debug("Creating main config, creating all nodes");
for( i=1; i<NUM_CM; i++ ) {
if( !xml_elements[config_type][i].parent ) {
if( i != CM_ROOT ) debug("WARNING: non-root parentless node found in xml_elements[%d][], this should NOT happen!", config_type);
else debug("creating root node '%s', behold!", xml_elements[config_type][i].name);
node = xmlNewNode(NULL, xml_elements[config_type][i].name);
xmlDocSetRootElement(doc, node);
} else {
debug("creating node '%s' under parent '%s'", xml_elements[config_type][i].name, xml_elements[config_type][xml_elements[config_type][i].parent].name);
node = xmlNewTextChild(xml_get_node(config_type, doc, xml_elements[config_type][i].parent), NULL, xml_elements[config_type][i].name, "");
}
/* set appropriate attributes, if any, and set their default values */
for( a = 0; a < NUM_ATTRN; a++ ) {
if( (xml_elements[config_type][i].attributes & xml_attributes[a].attribute) ) {
debug("adding attribute '%s=\"%s\"'", xml_attributes[a].name, xml_attributes[a].default_value);
xmlNewProp(node, xml_attributes[a].name, xml_attributes[a].default_value);
}
}
debug("Node '%s': setting default value '%s'", node->name, xml_elements[config_type][i].default_value);
xmlNodeSetContent(node, xml_elements[config_type][i].default_value);
}
} else {
debug("We're not creating main config, so we just create root element for now");
node = xmlNewNode(NULL, xml_elements[config_type][CM_ROOT].name);
xmlDocSetRootElement(doc, node);
}
return doc;
}
/* checks if the node name is in xml_elements[config_type][] table */
gint xml_is_valid_node(gint config_type, xmlChar *nodename) {
gint i;
for( i = 0; i < NUM_CM; i++ ) {
if( !xmlStrcmp(xml_elements[config_type][i].name, nodename) ) return 1;
}
return 0;
}
/* checks if the attribute name is in xml_attributes[] table */
gint xml_is_valid_attr(gint config_type, xmlNodePtr node, xmlChar *attrname) {
gint a, valid = 0;
gint n = xml_find_option_num_by_name(config_type, (xmlChar *)node->name);
for( a = 0; a < NUM_ATTRN; a++ ) {
if( (xml_elements[config_type][n].attributes & xml_attr_bits[a]) ) valid++;
}
if(valid) return 1;
return 0;
}
/* this is an auxiliary function used by xml_cleanup_config() to recursively
parse all nodes below given node (inclusively) */
void xml_aux_cleanup_node(gint config_type, xmlNodePtr node)
{
xmlNodePtr cur;
xmlAttrPtr attr;
for (cur = node; cur; cur = cur->next) {
if (cur->type == XML_ELEMENT_NODE) { /* only test for actual xml nodes */
if( !xml_is_valid_node(config_type, (xmlChar *)cur->name) ) {
debug("Node '%s' is invalid, removing from xml!", cur->name);
/* whole next big 'if' just removes the invalid node from a linked list
of nodes inside libxml structure, so we can free it */
if( cur->next ) {
if( cur->prev ) cur->prev->next = cur->next;
else cur->parent->children = cur->next;
} else {
if( cur->prev ) cur->prev->next = NULL;
// else cur->parent->children = NULL;
else cur = NULL;
}
/* all done, now let's ditch the stinker! */
xmlFreeNode(cur);
} else {
/* node is valid, now let's check its attributes */
for( attr = cur->properties; attr; attr = attr->next ) {
if( !xml_is_valid_attr(config_type, cur, (xmlChar *)attr->name) ) {
debug("Attribute '%s' (from node '%s') is invalid, removing from xml!", attr->name, cur->name);
if( attr->next ) {
if( attr->prev ) attr->prev->next = attr->next;
else attr->parent->properties = attr->next;
} else {
if( attr->prev ) attr->prev->next = NULL;
else attr->parent->properties = NULL;
}
/* all done, now let's ditch the stinker! */
xmlFreeProp(attr);
}
}
}
}
/* recursive call to clean up children of children of children of ... :) */
if (cur) {
xml_aux_cleanup_node(config_type, cur->children);
}
}
}
/* this will remove all unknown (not defined in xml_elements[config_type][]) nodes from the
config */
void xml_cleanup_config(gint config_type) {
xmlNodePtr cur;
xmlDocPtr doc = xml_doc(config_type);
if( !doc ) {
debug("No document!");
return;
}
if( !(cur = xmlDocGetRootElement(doc)) ) {
debug("root node could not be acquired!");
return;
}
debug("Removing unknown nodes from the config...");
xml_aux_cleanup_node(config_type, cur);
}
/* this returns content of xml node, specified by xpath from doc.
return type is either gchar* or gint, depending on "type" attribute of
the node, defaulting to gint */
/* if there are more than one nodes corresponding to xpath, value of first one
is returned, rest is ignored */
gchar *xml_get_option(gint config_type, gint node_cn) {
xmlNodePtr node;
if( !xml_doc(config_type) ) {
debug("XML doc == NULL!");
return NULL;
}
if( !(node = xml_get_node(config_type, xml_doc(config_type), node_cn)) ) {
debug("XML node not found: %s", xml_elements[config_type][node_cn].name);
if( !(node = xml_create_node(config_type, node_cn)) ) {
debug("error - node still not found, returning NULL...");
return 0;
}
}
return xmlNodeGetContent(node);
}
/* this replaces content of xml node, specified by xpath from doc with value.
function returns 1 on success, 0 on failure. will also set "type" property */
/* if there are more than one nodes corresponding to xpath, value of first one
is set, rest is ignored */
gint xml_set_option(gint config_type, gint node_cn, xmlChar *value) {
xmlNodePtr node;
if( !xml_doc(config_type) ) return 0;
if( !(node = xml_get_node(config_type, xml_doc(config_type), node_cn)) ) {
debug("XML node not found: %s", xml_elements[config_type][node_cn].name);
if( !(node = xml_create_node(config_type, node_cn)) ) {
debug("error - node still not found, returning NULL...");
return 0;
}
}
xmlNodeSetContent(node, value);
return 1;
}
gint xml_find_option_num_by_name(gint config_type, xmlChar *name) {
gint i;
for( i=1; i<NUM_CM; i++ ) {
if( !xmlStrcmp(xml_elements[config_type][i].name, name) ) return i;
}
return 0;
}
gint xml_aux_write_node(FILE *f, gint config_type, xmlNodePtr node, gint depth) {
xmlNodePtr curn;
xmlAttrPtr cura;
gint curcn;
for( curn = node; curn; curn = curn->next ) {
if( curn->type == XML_ELEMENT_NODE ) {
if( !(curcn = xml_get_node_cX_by_node(config_type, curn)) ) {
debug("unknown node %s found, not writing it", curn->name);
return 0;
}
if( !g_fprintf(f, "%s<%s", g_strnfill(depth, ' '), curn->name ) ) return 0;
for( cura = curn->properties; cura; cura = cura->next ) {
if( !g_fprintf(f, " %s=\"%s\"", cura->name, xmlGetProp(curn, cura->name)) ) return 0;
}
if( !g_fprintf(f, ">") ) return 0;
if( xml_elements[config_type][curcn].default_value ) {
if( !g_fprintf(f, "%s</%s>\n",
xmlNodeGetContent(curn), curn->name) ) return 0;
} else {
if( !g_fprintf(f, "\n") ) return 0;
/* recursive call to write children */
xml_aux_write_node(f, config_type, curn->children, depth + 1);
if( !g_fprintf(f, "%s</%s>\n", g_strnfill(depth, ' '), curn->name ) ) return 0;
}
}
}
return 1;
}
gint xml_write_config(gint config_type, gchar *filename) {
FILE *f;
xmlNodePtr node;
xmlDocPtr doc = xml_doc(config_type);
/* some sanity checks first... */
if( !doc ) {
debug("error: doc == NULL");
return 0;
}
if( !(node = xmlDocGetRootElement(doc)) ) {
debug("error: could not get root element from xmlDoc!");
return 0;
}
if( g_file_test(filename, G_FILE_TEST_EXISTS) ) {
if( !g_file_test(filename, G_FILE_TEST_IS_REGULAR) ) {
debug("config file is not a regular file: %s", filename);
return 0;
}
}
if( !(f = fopen(filename, "w")) ) {
debug("could not open file %s for writing, aborting save", filename);
return 0;
}
debug("file '%s' opened, starting to write contents", filename);
/* write xml header first */
if( !g_fprintf(f, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") ) return 0;
if( !xml_aux_write_node(f, config_type, node, 0) ) return 0;
fclose(f);
return 1;
}
xmlDocPtr xml_doc(gint type) {
switch (type) {
case CONFIG_MAIN: return config_main;
case CONFIG_SERVERS: return config_servers;
case CONFIG_BUDDIES: return config_buddies;
default: return NULL;
}
return NULL;
}
syntax highlighted by Code2HTML, v. 0.9.1