/** * @file * Auxiliary class to handle "session" XML files (augmented Folker). * It is a GObject that must be freed using \e g_object_unref. */ /* * Copyright (C) 2012 Otto-von-Guericke-Universität Magdeburg * * 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 3 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, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include "experiment-reader.h" static void experiment_reader_class_init(ExperimentReaderClass *klass); static void experiment_reader_init(ExperimentReader *klass); static void experiment_reader_finalize(GObject *gobject); static gint64 get_timepoint_by_ref(xmlDoc *doc, xmlChar *ref); static xmlNode *get_first_element(xmlNode *children, const gchar *name); static gboolean generic_foreach_topic(ExperimentReader *reader, xmlNodeSet *nodes, ExperimentReaderTopicCallback callback, gpointer data); static gint experiment_reader_contrib_cmp(const ExperimentReaderContrib *a, const ExperimentReaderContrib *b); /** @private */ #define EXPERIMENT_READER_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE((obj), EXPERIMENT_TYPE_READER, ExperimentReaderPrivate)) /** @private */ struct _ExperimentReaderPrivate { xmlDoc *doc; }; /** * @private * Will create \e experiment_reader_get_type and set * \e experiment_reader_parent_class */ G_DEFINE_TYPE(ExperimentReader, experiment_reader, G_TYPE_OBJECT); static void experiment_reader_class_init(ExperimentReaderClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS(klass); /* gobject_class->dispose = experiment_reader_dispose; */ gobject_class->finalize = experiment_reader_finalize; g_type_class_add_private(klass, sizeof(ExperimentReaderPrivate)); } static void experiment_reader_init(ExperimentReader *klass) { klass->priv = EXPERIMENT_READER_GET_PRIVATE(klass); klass->priv->doc = NULL; } static void experiment_reader_finalize(GObject *gobject) { ExperimentReader *reader = EXPERIMENT_READER(gobject); if (reader->priv->doc != NULL) xmlFreeDoc(reader->priv->doc); /* Chain up to the parent class */ G_OBJECT_CLASS(experiment_reader_parent_class)->finalize(gobject); } static gint64 get_timepoint_by_ref(xmlDoc *doc, xmlChar *ref) { xmlChar expr[255]; xmlXPathContext *xpathCtx; xmlXPathObject *xpathObj; double value; xpathCtx = xmlXPathNewContext(doc); assert(xpathCtx != NULL); /** @todo precompile XPath expression */ xmlStrPrintf(expr, sizeof(expr), (const xmlChar *)"/session/timeline/" "timepoint[@timepoint-id = '%s']/" "@absolute-time", ref); xpathObj = xmlXPathEvalExpression(expr, xpathCtx); assert(xpathObj != NULL); value = xmlXPathCastToNumber(xpathObj); xmlXPathFreeObject(xpathObj); xmlXPathFreeContext(xpathCtx); return (gint64)(value*1000.); } static xmlNode * get_first_element(xmlNode *children, const gchar *name) { for (xmlNode *cur = children; cur != NULL; cur = cur->next) if (cur->type == XML_ELEMENT_NODE && !g_strcmp0((const gchar *)cur->name, name)) return cur; return NULL; } static gboolean generic_foreach_topic(ExperimentReader *reader, xmlNodeSet *nodes, ExperimentReaderTopicCallback callback, gpointer data) { if (nodes == NULL) return TRUE; for (int i = 0; i < nodes->nodeNr; i++) { xmlNode *cur = nodes->nodeTab[i]; xmlNode *contrib = get_first_element(cur->children, "contribution"); assert(cur != NULL && cur->type == XML_ELEMENT_NODE); xmlChar *topic_id = xmlGetProp(cur, (const xmlChar *)"id"); gint64 start_time = -1; if (contrib != NULL) { xmlChar *contrib_start_ref; contrib_start_ref = xmlGetProp(contrib, (const xmlChar *)"start-reference"); start_time = get_timepoint_by_ref(reader->priv->doc, contrib_start_ref); xmlFree(contrib_start_ref); } callback(reader, (const gchar *)topic_id, start_time, data); xmlFree(topic_id); } return FALSE; } static gint experiment_reader_contrib_cmp(const ExperimentReaderContrib *a, const ExperimentReaderContrib *b) { if (a->start_time < b->start_time) return -1; if (a->start_time > b->start_time) return 1; return 0; } /* * API */ /** * @brief Constructs a new ExperimentReader object * * @param filename Filename of XML file to open * @return A new \e ExperimentReader object. Free with \e g_object_unref. */ ExperimentReader * experiment_reader_new(const gchar *filename) { ExperimentReader *reader; reader = EXPERIMENT_READER(g_object_new(EXPERIMENT_TYPE_READER, NULL)); reader->priv->doc = xmlParseFile(filename); if (reader->priv->doc == NULL) { g_object_unref(G_OBJECT(reader)); return NULL; } /** @todo validate against session.dtd */ return reader; } GList * experiment_reader_get_contributions_by_speaker(ExperimentReader *reader, const gchar *speaker) { GList *list = NULL; xmlXPathContext *xpathCtx; xmlXPathObject *xpathObj; xmlChar expr[255]; xpathCtx = xmlXPathNewContext(reader->priv->doc); /* Evaluate xpath expression */ xmlStrPrintf(expr, sizeof(expr), (const xmlChar *) "//contribution[@speaker-reference = " "/session/speakers/speaker[name = '%s']/@speaker-id]", speaker); xpathObj = xmlXPathEvalExpression(expr, xpathCtx); for (int i = 0; i < xpathObj->nodesetval->nodeNr; i++) { xmlNode *contrib = xpathObj->nodesetval->nodeTab[i]; xmlChar *ref; gint64 start_time; gchar *text = NULL; ref = xmlGetProp(contrib, (const xmlChar *)"start-reference"); start_time = get_timepoint_by_ref(reader->priv->doc, ref); xmlFree(ref); for (xmlNode *cur = contrib->children; cur != NULL; cur = cur->next) { xmlChar *content; gchar *new; switch (cur->type) { case XML_TEXT_NODE: content = xmlNodeGetContent(cur); new = g_strconcat(text != NULL ? text : "", g_strstrip((gchar *)content), " ", NULL); g_free(text); text = new; xmlFree(content); break; case XML_ELEMENT_NODE: if (!xmlStrcmp(cur->name, (const xmlChar *)"pause")) { xmlChar *duration = xmlGetProp(cur, (const xmlChar *)"duration"); if (duration != NULL) { if (!xmlStrcmp(duration, (const xmlChar *)"micro") || !xmlStrcmp(duration, (const xmlChar *)"short")) new = g_strconcat(text != NULL ? text : "", " ...", NULL); else new = g_strconcat(text != NULL ? text : "", "\n", NULL); g_free(text); text = new; xmlFree(duration); } } else if (!xmlStrcmp(cur->name, (const xmlChar *)"time")) { if (text != NULL) { ExperimentReaderContrib *contrib = g_malloc(sizeof(ExperimentReaderContrib) + strlen(text) + 1); contrib->start_time = start_time; g_stpcpy(contrib->text, text); list = g_list_insert_sorted(list, contrib, (GCompareFunc)experiment_reader_contrib_cmp); g_free(text); text = NULL; } ref = xmlGetProp(cur, (const xmlChar *)"timepoint-reference"); start_time = get_timepoint_by_ref(reader->priv->doc, ref); xmlFree(ref); } break; default: break; } } if (text != NULL) { ExperimentReaderContrib *contrib = g_malloc(sizeof(ExperimentReaderContrib) + strlen(text) + 1); contrib->start_time = start_time; g_stpcpy(contrib->text, text); list = g_list_insert_sorted(list, contrib, (GCompareFunc)experiment_reader_contrib_cmp); g_free(text); } } xmlXPathFreeObject(xpathObj); xmlXPathFreeContext(xpathCtx); return list; } GList * experiment_reader_get_contribution_by_time(GList *contribs, gint64 timept) { for (GList *cur = contribs; cur != NULL; cur = cur->next) { ExperimentReaderContrib *contrib = (ExperimentReaderContrib *)cur->data; if (contrib->start_time >= timept || cur->next == NULL) return cur; } return NULL; } void experiment_reader_free_contributions(GList *contribs) { for (GList *cur = contribs; cur != NULL; cur = cur->next) g_free(cur->data); g_list_free(contribs); } /** * Calls \e callback with \e userdata for each \b topic in the \b greeting * section of the experiment. * * @param reader \e ExperimentReader instance * @param callback Function to invoke * @param userdata User data to pass to \e callback */ void experiment_reader_foreach_greeting_topic(ExperimentReader *reader, ExperimentReaderTopicCallback callback, gpointer userdata) { xmlXPathContext *xpathCtx; xmlXPathObject *xpathObj; xpathCtx = xmlXPathNewContext(reader->priv->doc); xpathObj = xmlXPathEvalExpression((const xmlChar *)"/session/greeting/topic", xpathCtx); generic_foreach_topic(reader, xpathObj->nodesetval, callback, userdata); xmlXPathFreeObject(xpathObj); xmlXPathFreeContext(xpathCtx); } /** * Calls \e callback with \e userdata for each \b topic in the * \b initial-narrative subsection of the \b experiment section of * the experiment. * * @param reader \e ExperimentReader instance * @param callback Function to invoke * @param userdata User data to pass to \e callback */ void experiment_reader_foreach_exp_initial_narrative_topic(reader, callback, userdata) ExperimentReader *reader; ExperimentReaderTopicCallback callback; gpointer userdata; { xmlXPathContext *xpathCtx; xmlXPathObject *xpathObj; xpathCtx = xmlXPathNewContext(reader->priv->doc); xpathObj = xmlXPathEvalExpression((const xmlChar *)"/session/experiment/" "initial-narrative/topic", xpathCtx); generic_foreach_topic(reader, xpathObj->nodesetval, callback, userdata); xmlXPathFreeObject(xpathObj); xmlXPathFreeContext(xpathCtx); } /** * Calls \e callback with \e userdata for each \b topic in a \b phase of * the \b last-minute subsection of the \b experiment section of * the experiment. * * @param reader \e ExperimentReader instance * @param phase \b Phase section (integer from 1 to 6) * @param callback Function to invoke * @param userdata User data to pass to \e callback */ void experiment_reader_foreach_exp_last_minute_phase_topic(reader, phase, callback, userdata) ExperimentReader *reader; gint phase; ExperimentReaderTopicCallback callback; gpointer userdata; { xmlXPathContext *xpathCtx; xmlXPathObject *xpathObj; xmlChar expr[255]; xpathCtx = xmlXPathNewContext(reader->priv->doc); /* Evaluate xpath expression */ xmlStrPrintf(expr, sizeof(expr), (const xmlChar *)"/session/experiment/last-minute/" "phase[@id = '%d']/topic", phase); xpathObj = xmlXPathEvalExpression(expr, xpathCtx); generic_foreach_topic(reader, xpathObj->nodesetval, callback, userdata); xmlXPathFreeObject(xpathObj); xmlXPathFreeContext(xpathCtx); } /** * Calls \e callback with \e userdata for each \b topic in the \b farewell * section of the experiment. * * @param reader \e ExperimentReader instance * @param callback Function to invoke * @param userdata User data to pass to \e callback */ void experiment_reader_foreach_farewell_topic(ExperimentReader *reader, ExperimentReaderTopicCallback callback, gpointer userdata) { xmlXPathContext *xpathCtx; xmlXPathObject *xpathObj; xpathCtx = xmlXPathNewContext(reader->priv->doc); xpathObj = xmlXPathEvalExpression((const xmlChar *)"/session/farewell/topic", xpathCtx); generic_foreach_topic(reader, xpathObj->nodesetval, callback, userdata); xmlXPathFreeObject(xpathObj); xmlXPathFreeContext(xpathCtx); }