mirror of https://github.com/Gnucash/gnucash
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
457 lines
12 KiB
457 lines
12 KiB
|
|
#include <config.h>
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <glib.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "gnc-xml-helper.h"
|
|
#include "sixtp-xml-write-utils.h"
|
|
|
|
#include "gnc-numeric.h"
|
|
#include "gnc-engine-util.h"
|
|
|
|
static short module = MOD_IO;
|
|
|
|
/* ============================================================== */
|
|
|
|
gboolean
|
|
xml_add_str(xmlNodePtr p, const char *tag, const char *str,
|
|
gboolean include_if_empty) {
|
|
xmlNodePtr child;
|
|
|
|
g_return_val_if_fail(p, FALSE);
|
|
g_return_val_if_fail(tag, FALSE);
|
|
if(!str && !include_if_empty) return(TRUE);
|
|
if((strlen(str) == 0) && !include_if_empty) return(TRUE);
|
|
|
|
child = xmlNewTextChild(p, NULL, tag, str);
|
|
g_return_val_if_fail(child, FALSE);
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
gboolean
|
|
xml_add_character(xmlNodePtr p, const char *tag, const char c) {
|
|
char str[2];
|
|
str[0] = c;
|
|
str[1] = '\0';
|
|
return(xml_add_str(p, tag, str, FALSE));
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
gboolean
|
|
xml_add_gint64(xmlNodePtr p, const char *tag, const gint64 value) {
|
|
xmlNodePtr val_xml;
|
|
char num_string[22];
|
|
|
|
g_return_val_if_fail(p, FALSE);
|
|
g_return_val_if_fail(tag, FALSE);
|
|
|
|
g_snprintf(num_string, sizeof (num_string), "%lld", value);
|
|
|
|
val_xml = xmlNewTextChild(p, NULL, tag, num_string);
|
|
g_return_val_if_fail(val_xml, FALSE);
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
gboolean
|
|
xml_add_gint32(xmlNodePtr p, const char *tag, const gint32 value) {
|
|
xmlNodePtr val_xml;
|
|
char num_string[22];
|
|
|
|
g_return_val_if_fail(p, FALSE);
|
|
g_return_val_if_fail(tag, FALSE);
|
|
|
|
g_snprintf(num_string, sizeof (num_string), "%d", value);
|
|
|
|
val_xml = xmlNewTextChild(p, NULL, tag, num_string);
|
|
g_return_val_if_fail(val_xml, FALSE);
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
/* ============================================================== */
|
|
/*
|
|
RLB writes:
|
|
We have to use guile because AFAICT, libc, and C in general isn't
|
|
smart enough to actually parse it's own output, especially not
|
|
portably (big surprise).
|
|
|
|
Linas writes:
|
|
I don't understand the claim; I'm just going to use
|
|
atof or strtod to accomplish this.
|
|
|
|
*/
|
|
|
|
gboolean
|
|
xml_add_double(xmlNodePtr p, const char *tag, const double value)
|
|
{
|
|
g_return_val_if_fail(p, FALSE);
|
|
g_return_val_if_fail(tag, FALSE);
|
|
|
|
|
|
#ifdef USE_GUILE_FOR_DOUBLE_CONVERSION
|
|
{
|
|
/* FIXME: NOT THREAD SAFE - USES STATIC DATA */
|
|
static SCM number_to_string;
|
|
static gboolean ready = FALSE;
|
|
const char *numstr;
|
|
|
|
if(!ready) {
|
|
number_to_string = gh_eval_str("number->string");
|
|
scm_protect_object(number_to_string);
|
|
ready = TRUE;
|
|
}
|
|
|
|
numstr = gh_scm2newstr(gh_call1(number_to_string, gh_double2scm(value)),
|
|
NULL);
|
|
|
|
if(!numstr) {
|
|
return(FALSE);
|
|
} else {
|
|
xmlNodePtr child = xmlNewTextChild(p, NULL, tag, numstr);
|
|
free((void *) numstr);
|
|
g_return_val_if_fail(child, FALSE);
|
|
}
|
|
}
|
|
|
|
#else /* don't USE_GUILE_FOR_DOUBLE_CONVERSION */
|
|
{
|
|
int len;
|
|
char prtbuf[80];
|
|
xmlNodePtr child;
|
|
|
|
/* we're just going to use plain-old libc for the double conversion.
|
|
* There was some question as to whether libc is accurate enough
|
|
* in its printf function for doubles, but I don't understand
|
|
* how it couldn't be ...
|
|
*/
|
|
len = snprintf (prtbuf, 80, "%24.18g", value);
|
|
if (80 <=len) return (FALSE);
|
|
|
|
child = xmlNewTextChild(p, NULL, tag, prtbuf);
|
|
g_return_val_if_fail(child, FALSE);
|
|
}
|
|
|
|
#endif /* USE_GUILE_FOR_DOUBLE_CONVERSION */
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
gboolean
|
|
xml_add_gnc_numeric(xmlNodePtr p, const char *tag, const gnc_numeric n) {
|
|
char *numstr;
|
|
xmlNodePtr child;
|
|
|
|
g_return_val_if_fail(p, FALSE);
|
|
g_return_val_if_fail(tag, FALSE);
|
|
|
|
/* fprintf(stderr, "WRITE GNUM S: %lld/%lld -> ", n.num, n.denom); */
|
|
|
|
numstr = gnc_numeric_to_string(n);
|
|
g_return_val_if_fail(numstr, FALSE);
|
|
|
|
/* fprintf(stderr, "%s\n", numstr); */
|
|
|
|
child = xmlNewTextChild(p, NULL, tag, numstr);
|
|
g_free(numstr); numstr = FALSE;
|
|
g_return_val_if_fail(child, FALSE);
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
gboolean
|
|
xml_add_guid(xmlNodePtr p, const char *tag, const GUID *guid) {
|
|
|
|
g_return_val_if_fail(p, FALSE);
|
|
g_return_val_if_fail(tag, FALSE);
|
|
g_return_val_if_fail(guid, FALSE);
|
|
|
|
{
|
|
const char *guidstr;
|
|
xmlNodePtr child;
|
|
|
|
if(!guid) {
|
|
guidstr = NULL;
|
|
} else {
|
|
guidstr = guid_to_string(guid);
|
|
g_return_val_if_fail(guidstr, FALSE);
|
|
}
|
|
|
|
child = xmlNewTextChild(p, NULL, tag, guidstr);
|
|
g_return_val_if_fail(child, FALSE);
|
|
if(guidstr) free((void *) guidstr);
|
|
}
|
|
return(TRUE);
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
gboolean
|
|
xml_add_editable_timespec(xmlNodePtr p,
|
|
const char *tag,
|
|
const Timespec *ts,
|
|
gboolean include_if_zero) {
|
|
xmlNodePtr timespec_xml;
|
|
xmlNodePtr secs_xml;
|
|
size_t num_written;
|
|
struct tm parsed_time;
|
|
time_t tmp_timet;
|
|
char secs_str[512]; /* This should be way bigger than we need.
|
|
Still, it's bogus, we ought to have
|
|
astrftime... */
|
|
|
|
g_return_val_if_fail(p, FALSE);
|
|
g_return_val_if_fail(tag, FALSE);
|
|
g_return_val_if_fail(ts, FALSE);
|
|
if(!include_if_zero && (ts->tv_sec == 0) && (ts->tv_nsec == 0)) return TRUE;
|
|
|
|
tmp_timet = ts->tv_sec;
|
|
if(!localtime_r(&tmp_timet, &parsed_time)) return(FALSE);
|
|
|
|
num_written = strftime(secs_str, sizeof(secs_str),
|
|
"%Y-%m-%d %H:%M:%S %z",
|
|
&parsed_time);
|
|
if(num_written == 0) return(FALSE);
|
|
|
|
timespec_xml= xmlNewTextChild(p, NULL, tag, NULL);
|
|
g_return_val_if_fail(timespec_xml, FALSE);
|
|
|
|
secs_xml = xmlNewTextChild(timespec_xml, NULL, "s", secs_str);
|
|
g_return_val_if_fail(secs_xml, FALSE);
|
|
|
|
if(ts->tv_nsec) {
|
|
xmlNodePtr nsec_xml;
|
|
char num_string[22];
|
|
|
|
g_snprintf(num_string, sizeof (num_string), "%ld", ts->tv_nsec);
|
|
|
|
nsec_xml = xmlNewTextChild(timespec_xml, NULL, "ns", num_string);
|
|
g_return_val_if_fail(nsec_xml, FALSE);
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
static gboolean
|
|
xml_add_binary(xmlNodePtr p,
|
|
const char *tag,
|
|
const gchar *format,
|
|
const void *data,
|
|
guint32 size)
|
|
{
|
|
|
|
xmlNodePtr value_xml;
|
|
|
|
g_return_val_if_fail(p, FALSE);
|
|
g_return_val_if_fail(tag, FALSE);
|
|
g_return_val_if_fail(format, FALSE);
|
|
g_return_val_if_fail(data, FALSE);
|
|
|
|
value_xml = xmlNewTextChild(p, NULL, tag, NULL);
|
|
g_return_val_if_fail(value_xml, FALSE);
|
|
|
|
if(size == 0) return(TRUE);
|
|
|
|
if(0 == strcmp(format, "hex")) {
|
|
/* Write out the chars as hex, buffering them in max 64 character
|
|
lines. I was going to use xmlNewTextChild, and xmlTextConcat,
|
|
but that doesn't seem to work, and looking at the source,
|
|
xmlNewTextChild doesn't set the node type to a type that
|
|
xmlTextConcat will recognize and allow. */
|
|
|
|
const guint max_line_len = 64;
|
|
xmlNodePtr data_xml = NULL;
|
|
GString *output;
|
|
guint32 i;
|
|
|
|
output = g_string_sized_new(max_line_len + 2);
|
|
|
|
for(i = 0; i < size; i++) {
|
|
g_string_sprintfa(output, "%x", (int) (((char *) data)[i]));
|
|
if(((i + 1) % max_line_len) == 0) {
|
|
data_xml = xmlNewTextChild(value_xml, NULL, "hex", output->str);
|
|
if(!data_xml) {
|
|
return(FALSE);
|
|
g_string_free(output, TRUE);
|
|
}
|
|
g_string_truncate(output, 0);
|
|
}
|
|
}
|
|
|
|
if(strlen(output->str) > 0) {
|
|
data_xml = xmlNewTextChild(value_xml, NULL, "hex", output->str);
|
|
if(!data_xml) {
|
|
g_string_free(output, TRUE);
|
|
return(FALSE);
|
|
}
|
|
}
|
|
g_string_free(output, TRUE);
|
|
|
|
} else {
|
|
PERR("unknown output format %s.\n", format);
|
|
return(FALSE);
|
|
}
|
|
return(TRUE);
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
static gboolean xml_add_kvp_value(xmlNodePtr p, kvp_value *val);
|
|
|
|
static gboolean
|
|
xml_add_kvp_glist(xmlNodePtr p, const char *tag, GList *lst) {
|
|
xmlNodePtr list_xml;
|
|
GList *cursor;
|
|
|
|
g_return_val_if_fail(p, FALSE);
|
|
g_return_val_if_fail(tag, FALSE);
|
|
g_return_val_if_fail(lst, FALSE);
|
|
|
|
list_xml = xmlNewTextChild(p, NULL, tag, NULL);
|
|
g_return_val_if_fail(list_xml, FALSE);
|
|
|
|
for(cursor = lst; cursor; cursor = cursor->next) {
|
|
kvp_value * val = (kvp_value *) cursor->data;
|
|
if(!xml_add_kvp_value(list_xml, val)) {
|
|
return(FALSE);
|
|
}
|
|
}
|
|
return(TRUE);
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
gboolean
|
|
xml_add_kvp_frame(xmlNodePtr p, const char *tag,
|
|
const kvp_frame *kvpf,
|
|
gboolean add_if_empty);
|
|
|
|
static gboolean
|
|
xml_add_kvp_value(xmlNodePtr p, kvp_value *val) {
|
|
|
|
g_return_val_if_fail(p, FALSE);
|
|
g_return_val_if_fail(val, FALSE);
|
|
|
|
switch(kvp_value_get_type(val)) {
|
|
case KVP_TYPE_GINT64:
|
|
return(xml_add_gint64(p, "gint64", kvp_value_get_gint64(val)));
|
|
break;
|
|
case KVP_TYPE_DOUBLE:
|
|
return(xml_add_double(p, "double", kvp_value_get_double(val)));
|
|
break;
|
|
case KVP_TYPE_NUMERIC:
|
|
return(xml_add_gnc_numeric(p, "numeric", kvp_value_get_numeric(val)));
|
|
break;
|
|
case KVP_TYPE_STRING:
|
|
return(xml_add_str(p, "string", kvp_value_get_string(val), TRUE));
|
|
break;
|
|
case KVP_TYPE_GUID:
|
|
return(xml_add_guid(p, "guid", kvp_value_get_guid(val)));
|
|
break;
|
|
case KVP_TYPE_BINARY:
|
|
{
|
|
guint64 size;
|
|
void *binary_data = kvp_value_get_binary(val, &size);
|
|
g_return_val_if_fail(binary_data, FALSE);
|
|
return(xml_add_binary(p, "binary", "hex", binary_data, size));
|
|
}
|
|
break;
|
|
case KVP_TYPE_GLIST:
|
|
return(xml_add_kvp_glist(p, "glist", kvp_value_get_glist(val)));
|
|
break;
|
|
case KVP_TYPE_FRAME:
|
|
return(xml_add_kvp_frame(p, "frame", kvp_value_get_frame(val), TRUE));
|
|
break;
|
|
default:
|
|
return(FALSE);
|
|
break;
|
|
};
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
static gboolean
|
|
xml_add_kvp_slot(xmlNodePtr p, const char *key, kvp_value *val) {
|
|
xmlNodePtr slot_xml;
|
|
xmlNodePtr key_xml;
|
|
|
|
g_return_val_if_fail(p, FALSE);
|
|
g_return_val_if_fail(key, FALSE);
|
|
g_return_val_if_fail(val, FALSE);
|
|
|
|
slot_xml = xmlNewTextChild(p, NULL, "s", NULL);
|
|
g_return_val_if_fail(slot_xml, FALSE);
|
|
|
|
key_xml = xmlNewTextChild(slot_xml, NULL, "k", key);
|
|
g_return_val_if_fail(key_xml, FALSE);
|
|
|
|
return(xml_add_kvp_value(slot_xml, val));
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
typedef struct {
|
|
xmlNodePtr node;
|
|
gint64 keycount;
|
|
} kvp_value_foreach_info;
|
|
|
|
static void
|
|
xml_add_kvp_value_foreach_adapter(const char *key,
|
|
kvp_value *value,
|
|
gpointer data) {
|
|
kvp_value_foreach_info *info = (kvp_value_foreach_info *) data;
|
|
xml_add_kvp_slot(info->node, key, value);
|
|
info->keycount++;
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
gboolean
|
|
xml_add_kvp_frame(xmlNodePtr p,
|
|
const char *tag,
|
|
const kvp_frame *kvpf,
|
|
gboolean add_if_empty) {
|
|
|
|
xmlNodePtr kvp_xml;
|
|
kvp_value_foreach_info info;
|
|
|
|
g_return_val_if_fail(p, FALSE);
|
|
g_return_val_if_fail(tag, FALSE);
|
|
g_return_val_if_fail(kvpf, FALSE);
|
|
|
|
kvp_xml = xmlNewNode(NULL, tag);
|
|
g_return_val_if_fail(kvp_xml, FALSE);
|
|
|
|
info.node = kvp_xml;
|
|
info.keycount = 0;
|
|
kvp_frame_for_each_slot((kvp_frame *) kvpf,
|
|
xml_add_kvp_value_foreach_adapter,
|
|
&info);
|
|
if(add_if_empty || info.keycount) {
|
|
xmlAddChild(p, kvp_xml);
|
|
} else {
|
|
xmlFreeNode(kvp_xml);
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|