2024-06-04 22:40:15 +02:00
|
|
|
/*
|
|
|
|
* Gem-graph client
|
|
|
|
*
|
|
|
|
* Desc: Model parsing functions
|
|
|
|
*
|
|
|
|
* Copyright (C) 2023 Jean Sirmai <jean@a-lec.org>
|
|
|
|
* Copyright (C) 2024 Adrien Bourmault <neox@a-lec.org>
|
|
|
|
*
|
|
|
|
* This file is part of Gem-graph.
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <libxml/tree.h>
|
|
|
|
#include <libxml/parser.h>
|
|
|
|
#include <libxml/xmlreader.h> // http://xmlsoft.org/examples/#parse1.c
|
|
|
|
// https://gnome.pages.gitlab.gnome.org/libxml2/devhelp/general.html
|
|
|
|
|
2024-06-05 22:36:44 +02:00
|
|
|
#include "base.h"
|
|
|
|
|
2024-06-04 22:40:15 +02:00
|
|
|
#define READ_SITE 1 << 0
|
|
|
|
#define READ_WEIGHT 1 << 1
|
|
|
|
#define READ_X 1 << 2
|
|
|
|
#define READ_Y 1 << 3
|
|
|
|
#define READ_Z 1 << 4
|
|
|
|
#define SUCCESSFUL_READ_ARROW_X (READ_SITE | READ_WEIGHT | READ_X)
|
|
|
|
#define SUCCESSFUL_READ_ARROW_XY (READ_SITE | READ_WEIGHT | READ_X | READ_Y)
|
|
|
|
#define SUCCESSFUL_READ_ARROW_XYZ (READ_SITE | READ_WEIGHT | READ_X | READ_Y | READ_Z)
|
|
|
|
|
|
|
|
static xmlDocPtr model;
|
|
|
|
static xmlHashTablePtr model_hashtable;
|
|
|
|
|
|
|
|
bool model_init(const char *content, size_t length, const char *basename)
|
|
|
|
{
|
|
|
|
xmlNode *node;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* this initialize the library and check potential ABI mismatches
|
|
|
|
* between the version it was compiled for and the actual shared
|
|
|
|
* library used.
|
|
|
|
*/
|
|
|
|
LIBXML_TEST_VERSION
|
|
|
|
|
|
|
|
model = xmlReadMemory(content, length, basename, NULL, 0);
|
|
|
|
|
|
|
|
if (model == NULL ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
node = xmlDocGetRootElement(model);
|
|
|
|
|
|
|
|
if (node == NULL) {
|
|
|
|
g_printerr("Empty XML model !\n");
|
|
|
|
xmlFreeDoc(model);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xmlStrcmp(node->name, (xmlChar *) "gem-graph-model")) {
|
|
|
|
g_printerr("document of the wrong type, root node != gem-graph-model\n");
|
|
|
|
xmlFreeDoc(model);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
model_hashtable = xmlHashCreate(0);
|
|
|
|
|
|
|
|
if (model_hashtable == NULL) {
|
|
|
|
g_printerr("Can't create model hash table !\n");
|
|
|
|
xmlFreeDoc(model);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool model_shutdown(void)
|
|
|
|
{
|
|
|
|
xmlFreeDoc(model);
|
|
|
|
xmlHashFree(model_hashtable, NULL);
|
|
|
|
|
|
|
|
// Cleanup function for the XML library
|
|
|
|
xmlCleanupParser();
|
|
|
|
|
|
|
|
// This is to debug memory for regression tests
|
|
|
|
xmlMemoryDump();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
static inline xmlNodePtr getNextChild(xmlNodePtr node, xmlChar *last)
|
|
|
|
{
|
|
|
|
while (node != NULL && xmlStrcmp(node->name, last)) {
|
|
|
|
// //printf(" <>--- line n°%lu <%s>\n", xmlGetLineNo(node), node->name);
|
|
|
|
node = node->next;
|
|
|
|
}
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline xmlChar* splitStrAtSlash(xmlChar *toSplit)
|
|
|
|
{
|
|
|
|
toSplit = (xmlChar *)xmlStrchr(toSplit, '/');
|
|
|
|
toSplit = xmlStrsub (toSplit, 1, xmlStrlen(toSplit));
|
|
|
|
return toSplit;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline xmlChar* getFirstTag(xmlChar *path)
|
|
|
|
{
|
|
|
|
xmlChar *preop = path;
|
|
|
|
path = (xmlChar *)xmlStrchr(path, '/');
|
|
|
|
path = xmlStrsub (path, 1, xmlStrlen(path));
|
|
|
|
|
2024-06-05 22:36:44 +02:00
|
|
|
//printf("%s = %s + / + %s\n", preop, xmlStrsub (preop, 0, xmlStrlen(preop) - xmlStrlen(path) - 1), path);
|
2024-06-04 22:40:15 +02:00
|
|
|
|
|
|
|
return xmlStrsub (preop, 0, xmlStrlen(preop) - xmlStrlen(path) - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline xmlChar* getLastTag(xmlChar *path)
|
|
|
|
{
|
|
|
|
while ((ulong)xmlStrchr (path, '/'))
|
|
|
|
path = splitStrAtSlash((xmlChar *)path);
|
|
|
|
|
|
|
|
// //printf("last tag in the path = <%s>\n", path);
|
|
|
|
return path; // which is no more the given path but only its last tag !
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
static xmlNodePtr model_get_node(xmlChar *path)
|
|
|
|
{
|
|
|
|
xmlNodePtr node;
|
|
|
|
xmlChar *extrait;
|
2024-06-05 22:36:44 +02:00
|
|
|
xmlChar *reste, *last;
|
2024-06-04 22:40:15 +02:00
|
|
|
|
|
|
|
// Lookup for node from path in hash table
|
|
|
|
node = xmlHashLookup(model_hashtable, path);
|
|
|
|
|
|
|
|
// Found a node in hash table
|
|
|
|
if (node) {
|
|
|
|
return node;
|
|
|
|
|
|
|
|
// no node found in hash table
|
|
|
|
} else {
|
|
|
|
reste = path;
|
|
|
|
last = getLastTag(reste);
|
|
|
|
node = xmlDocGetRootElement(model);
|
|
|
|
|
|
|
|
while (xmlStrchr (reste, '/')) {
|
|
|
|
extrait = getFirstTag(reste);
|
|
|
|
reste = splitStrAtSlash((xmlChar *)reste);
|
|
|
|
node = node->xmlChildrenNode;
|
|
|
|
node = getNextChild(node, extrait);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(node && xmlStrcmp(node->name, last)) {
|
|
|
|
node = node->xmlChildrenNode;
|
|
|
|
|
|
|
|
while (node && xmlStrcmp(node->name, last)) {
|
|
|
|
node = node->next;
|
|
|
|
}
|
|
|
|
xmlHashAddEntry (model_hashtable, path, node);
|
|
|
|
}
|
|
|
|
|
|
|
|
return node;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
static inline long model_get_node_long_attrib(xmlNodePtr node, char *id)
|
|
|
|
{
|
|
|
|
xmlAttr *attribute;
|
|
|
|
xmlChar* value;
|
|
|
|
long ret_value;
|
|
|
|
|
|
|
|
if (node && node->properties) {
|
|
|
|
attribute = node->properties;
|
|
|
|
while(attribute && attribute->name && attribute->children) {
|
|
|
|
if (!xmlStrcmp(attribute->name, (const xmlChar *)id)) {
|
|
|
|
value = xmlNodeListGetString(node->doc, attribute->children, 1);
|
|
|
|
ret_value = strtol((char *)value, NULL, 0);
|
|
|
|
xmlFree(value);
|
|
|
|
return ret_value;
|
|
|
|
}
|
|
|
|
attribute = attribute->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool model_get_node_str_attrib(xmlNodePtr node,
|
|
|
|
char *id,
|
|
|
|
char *dest)
|
|
|
|
{
|
|
|
|
xmlAttr *attribute;
|
|
|
|
xmlChar* value;
|
|
|
|
|
|
|
|
if (node && node->properties) {
|
|
|
|
attribute = node->properties;
|
|
|
|
while(attribute && attribute->name && attribute->children) {
|
|
|
|
if (!xmlStrcmp(attribute->name, (const xmlChar *)id)) {
|
|
|
|
value = xmlNodeListGetString(node->doc, attribute->children, 1);
|
2024-06-05 22:36:44 +02:00
|
|
|
strcpy(dest, (char *)value);
|
2024-06-04 22:40:15 +02:00
|
|
|
xmlFree(value);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
attribute = attribute->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
char model_get_dim(void)
|
|
|
|
{
|
2024-06-05 22:36:44 +02:00
|
|
|
// xmlAttr *attribute;
|
|
|
|
// xmlChar* value;
|
2024-06-04 22:40:15 +02:00
|
|
|
xmlNodePtr node = model_get_node(
|
|
|
|
(xmlChar *)"parameters/space-param/dimension");
|
|
|
|
|
|
|
|
if (xmlHasProp (node, (xmlChar *) "z")) return 3;
|
|
|
|
if (xmlHasProp (node, (xmlChar *) "y")) return 2;
|
|
|
|
if (xmlHasProp (node, (xmlChar *) "x")) return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
long model_get_dim_value(const char *axis)
|
|
|
|
{
|
2024-06-05 22:36:44 +02:00
|
|
|
// xmlAttr *attribute;
|
|
|
|
// xmlChar *value;
|
|
|
|
// long ret_value;
|
2024-06-04 22:40:15 +02:00
|
|
|
xmlNodePtr node = model_get_node(
|
|
|
|
(xmlChar *)"parameters/space-param/dimension");
|
|
|
|
|
2024-06-05 22:36:44 +02:00
|
|
|
return model_get_node_long_attrib(node, (char *)axis);
|
2024-06-04 22:40:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
char model_get_multiplicity(void)
|
|
|
|
{
|
2024-06-05 22:36:44 +02:00
|
|
|
// xmlAttr *attribute;
|
|
|
|
// xmlChar* value;
|
2024-06-04 22:40:15 +02:00
|
|
|
xmlNodePtr node = model_get_node(
|
|
|
|
(xmlChar *)"parameters/space-param/site_multiplicity");
|
|
|
|
|
|
|
|
if (node->children)
|
|
|
|
if (node->children->content)
|
|
|
|
return (char)strtol((char *)node->children->content, NULL, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool model_get_next_state(char *new_state_id)
|
|
|
|
{
|
|
|
|
static xmlNodePtr cur_node = NULL;
|
2024-06-05 22:36:44 +02:00
|
|
|
// xmlAttr *attribute;
|
|
|
|
// xmlChar *value;
|
2024-06-04 22:40:15 +02:00
|
|
|
|
|
|
|
if (cur_node == NULL) {
|
|
|
|
// Get first state
|
|
|
|
cur_node = model_get_node((xmlChar *)"savedstates/state");
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// Get next state
|
|
|
|
if (cur_node->next)
|
|
|
|
cur_node = cur_node->next;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lookup in properties
|
|
|
|
if (model_get_node_str_attrib(cur_node, "id", new_state_id))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
cur_node = NULL;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
long model_get_state_arrows_count(const char *state_id)
|
|
|
|
{
|
|
|
|
xmlNodePtr cur_node = NULL;
|
2024-06-05 22:36:44 +02:00
|
|
|
// xmlAttr *attribute;
|
2024-06-04 22:40:15 +02:00
|
|
|
long value = 0;
|
|
|
|
bool found = false;
|
|
|
|
char temp_char[25];
|
2024-06-05 22:36:44 +02:00
|
|
|
// uint check = 0; // bit field checker
|
2024-06-04 22:40:15 +02:00
|
|
|
|
|
|
|
//printf("NEW CALL : cur_node = %p\n", cur_node);
|
|
|
|
|
|
|
|
assert(state_id);
|
|
|
|
|
|
|
|
// Get first state node
|
|
|
|
cur_node = model_get_node((xmlChar *)"savedstates/state");
|
|
|
|
|
|
|
|
// Lookup in properties
|
|
|
|
while (cur_node && cur_node->properties) {
|
2024-06-05 22:36:44 +02:00
|
|
|
// attribute = cur_node->properties; < usage ?
|
2024-06-04 22:40:15 +02:00
|
|
|
|
|
|
|
// Look for the id attribute
|
2024-06-05 22:36:44 +02:00
|
|
|
if (model_get_node_str_attrib(cur_node, "id", (char *)&temp_char)) {
|
|
|
|
if (!xmlStrcmp((const xmlChar *)temp_char, (const xmlChar *)state_id)) {
|
2024-06-04 22:40:15 +02:00
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cur_node = cur_node->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the state has been found
|
|
|
|
if (!found) {
|
|
|
|
cur_node = NULL;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Count arrows
|
|
|
|
if (cur_node->children) {
|
|
|
|
cur_node = cur_node->children;
|
|
|
|
while (cur_node) {
|
|
|
|
if (!xmlStrcmp(cur_node->name, (const xmlChar *)"arrow"))
|
|
|
|
value++;
|
|
|
|
cur_node = cur_node->next;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool model_get_next_arrow(struct arrow_t *new_arrow,
|
|
|
|
const char *state_id,
|
|
|
|
char dimension)
|
|
|
|
{
|
|
|
|
static xmlNodePtr cur_node = NULL;
|
|
|
|
xmlAttr *attribute;
|
|
|
|
xmlChar *value;
|
|
|
|
bool found = false;
|
|
|
|
char temp_char[25];
|
|
|
|
uint check = 0; // bit field checker
|
|
|
|
|
|
|
|
//printf("NEW CALL : cur_node = %p\n", cur_node);
|
|
|
|
|
|
|
|
assert(new_arrow);
|
|
|
|
assert(state_id);
|
|
|
|
|
|
|
|
if (cur_node == NULL) {
|
|
|
|
// Get first state node
|
|
|
|
cur_node = model_get_node((xmlChar *)"savedstates/state");
|
|
|
|
|
|
|
|
// Lookup in properties
|
|
|
|
while (cur_node && cur_node->properties) {
|
|
|
|
attribute = cur_node->properties;
|
|
|
|
|
|
|
|
// Look for the id attribute
|
2024-06-05 22:36:44 +02:00
|
|
|
if (model_get_node_str_attrib(cur_node, "id", (char *)&temp_char)) {
|
|
|
|
if (!xmlStrcmp((xmlChar *)temp_char, (const xmlChar *)state_id)) {
|
2024-06-04 22:40:15 +02:00
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cur_node = cur_node->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the state has been found
|
|
|
|
if (!found) {
|
|
|
|
cur_node = NULL;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get first arrow
|
|
|
|
if (cur_node->children) {
|
|
|
|
cur_node = cur_node->children;
|
|
|
|
|
|
|
|
found = false;
|
|
|
|
while (cur_node && cur_node->name) {
|
|
|
|
if (!xmlStrcmp(cur_node->name, (const xmlChar *)"arrow")) {
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
cur_node = cur_node->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the state has been found
|
|
|
|
if (!found) {
|
|
|
|
cur_node = NULL;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// Get next arrow
|
|
|
|
found = false;
|
|
|
|
while (cur_node->next) {
|
|
|
|
cur_node = cur_node->next;
|
|
|
|
if (!xmlStrcmp(cur_node->name, (const xmlChar *)"arrow")) {
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the state has been found
|
|
|
|
if (!found) {
|
|
|
|
cur_node = NULL;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//printf("DURING CALL : cur_node = %p\n", cur_node);
|
|
|
|
//printf("DURING CALL : cur_node->name = %s\n", cur_node->name);
|
|
|
|
|
|
|
|
// Lookup in properties
|
|
|
|
if (cur_node && cur_node->properties) {
|
|
|
|
attribute = cur_node->properties;
|
|
|
|
|
|
|
|
while(attribute && attribute->name && attribute->children) {
|
|
|
|
//printf("attr name : %s\n", attribute->name);
|
|
|
|
if (!xmlStrcmp(attribute->name, (const xmlChar *)"site")) {
|
|
|
|
value = xmlNodeListGetString(cur_node->doc, attribute->children, 1);
|
|
|
|
new_arrow->site = strtol((char *)value, NULL, 0);
|
|
|
|
xmlFree(value);
|
|
|
|
check |= READ_SITE;
|
|
|
|
}
|
|
|
|
if (!xmlStrcmp(attribute->name, (const xmlChar *)"weight")) {
|
|
|
|
value = xmlNodeListGetString(cur_node->doc, attribute->children, 1);
|
|
|
|
new_arrow->load = strtol((char *)value, NULL, 0);
|
|
|
|
xmlFree(value);
|
|
|
|
check |= READ_WEIGHT;
|
|
|
|
}
|
|
|
|
if (!xmlStrcmp(attribute->name, (const xmlChar *)"x")) {
|
|
|
|
value = xmlNodeListGetString(cur_node->doc, attribute->children, 1);
|
|
|
|
new_arrow->x = strtol((char *)value, NULL, 0);
|
|
|
|
xmlFree(value);
|
|
|
|
check |= READ_X;
|
|
|
|
}
|
|
|
|
if (!xmlStrcmp(attribute->name, (const xmlChar *)"y")) {
|
|
|
|
value = xmlNodeListGetString(cur_node->doc, attribute->children, 1);
|
|
|
|
new_arrow->y = strtol((char *)value, NULL, 0);
|
|
|
|
xmlFree(value);
|
|
|
|
check |= READ_Y;
|
|
|
|
}
|
|
|
|
if (!xmlStrcmp(attribute->name, (const xmlChar *)"z")) {
|
|
|
|
value = xmlNodeListGetString(cur_node->doc, attribute->children, 1);
|
|
|
|
new_arrow->z = strtol((char *)value, NULL, 0);
|
|
|
|
xmlFree(value);
|
|
|
|
check |= READ_Z;
|
|
|
|
}
|
|
|
|
attribute = attribute->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(dimension) {
|
|
|
|
case 3:
|
|
|
|
return (bool)(check & SUCCESSFUL_READ_ARROW_XYZ);
|
|
|
|
case 2:
|
|
|
|
return (bool)(check & SUCCESSFUL_READ_ARROW_XY);
|
|
|
|
case 1:
|
|
|
|
return (bool)(check & SUCCESSFUL_READ_ARROW_X);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cur_node = NULL;
|
|
|
|
return false;
|
|
|
|
}
|