new geometry implementation + API update

This commit is contained in:
Alexey Fedoseev
2024-06-12 17:15:40 +03:00
parent 6cd92f21a4
commit f74369afb8
8 changed files with 1689 additions and 456 deletions

View File

@@ -7,7 +7,9 @@ if(!LibXml2_FOUND)
message(FATAL_ERROR "Cannot find libxml2 library")
endif()
add_library(cyberiadaml SHARED cyberiadaml.c utf8enc.c cyb_types.c cyb_string.c)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D__DEBUG__")
add_library(cyberiadaml SHARED cyberiadaml.c utf8enc.c cyb_types.c cyb_string.c geometry.c)
target_include_directories(cyberiadaml PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include/cyberiada>

38
cyb_error.h Normal file
View File

@@ -0,0 +1,38 @@
/* -----------------------------------------------------------------------------
* The Cyberiada GraphML library implemention
*
* Error handling
*
* Copyright (C) 2024 Alexey Fedoseev <aleksey@fedoseev.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser 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
* Lesser 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 https://www.gnu.org/licenses/
*
* ----------------------------------------------------------------------------- */
#ifndef __CYBERIADA_ERROR_H
#define __CYBERIADA_ERROR_H
#ifdef __DEBUG__
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
#else
#define DEBUG(...)
#endif
#ifndef __SILENT__
#define ERROR(...) fprintf(stderr, __VA_ARGS__)
#else
#define ERROR(...)
#endif
#endif

View File

@@ -38,7 +38,7 @@ int cyberiada_stack_update_top_key(CyberiadaStack** stack, const char* new_key
if (!stack || !*stack) {
return -1;
}
(*stack)->key = new_key;
(*stack)->key = (void*)new_key;
return 0;
}
@@ -98,7 +98,7 @@ int cyberiada_list_add(CyberiadaList** list, const char* key, void* data)
return -1;
}
new_item = (CyberiadaList*)malloc(sizeof(CyberiadaList));
new_item->key = key;
new_item->key = (void*)key;
new_item->data = data;
new_item->next = NULL;
if (!*list) {
@@ -119,7 +119,7 @@ void* cyberiada_list_find(CyberiadaList** list, const char* key)
}
item = *list;
while (item) {
if (item->key && strcmp(item->key, key) == 0) {
if (item->key && strcmp((const char*)item->key, key) == 0) {
return item->data;
}
item = item->next;
@@ -140,3 +140,49 @@ int cyberiada_list_free(CyberiadaList** list)
}
return 0;
}
int cyberiada_queue_add(CyberiadaQueue** queue, void* key, void* data)
{
CyberiadaQueue* new_item = (CyberiadaQueue*)malloc(sizeof(CyberiadaQueue));
memset(new_item, 0, sizeof(CyberiadaQueue));
new_item->key = key;
new_item->data = data;
new_item->next = (*queue);
*queue = new_item;
return 0;
}
int cyberiada_queue_get(CyberiadaQueue** queue, void** key, void** data)
{
CyberiadaQueue *q, *prev = NULL;
if (!queue || !key || !data) return 1;
q = *queue;
while (q->next) {
prev = q;
q = q->next;
}
*key = q->key;
*data = q->data;
free(q);
if (prev) {
prev->next = NULL;
} else {
*queue = NULL;
}
return 0;
}
int cyberiada_queue_free(CyberiadaQueue** queue)
{
CyberiadaQueue* item;
if (!queue) {
return 0;
}
while(*queue) {
item = *queue;
*queue = item->next;
free(item);
}
return 0;
}

View File

@@ -32,7 +32,7 @@ extern "C" {
* ----------------------------------------------------------------------------- */
struct _CyberiadaStruct {
const char* key;
void* key;
void* data;
struct _CyberiadaStruct* next;
};
@@ -53,6 +53,12 @@ extern "C" {
void* cyberiada_list_find(CyberiadaList** list, const char* key);
int cyberiada_list_free(CyberiadaList** list);
typedef struct _CyberiadaStruct CyberiadaQueue;
int cyberiada_queue_add(CyberiadaQueue** queue, void* key, void* data);
int cyberiada_queue_get(CyberiadaQueue** queue, void** key, void**data);
int cyberiada_queue_free(CyberiadaQueue** queue);
#ifdef __cplusplus
}
#endif

View File

@@ -35,6 +35,8 @@
#include "utf8enc.h"
#include "cyb_types.h"
#include "cyb_string.h"
#include "geometry.h"
#include "cyb_error.h"
/* -----------------------------------------------------------------------------
* Cyberiada parser constants
@@ -173,16 +175,9 @@
/* Misc. constants */
#define PSEUDO_NODE_SIZE 20
#define DEFAULT_NODE_SIZE 100
#define EMPTY_TITLE ""
#ifdef __DEBUG__
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
#else
#define DEBUG(...)
#endif
#define ERROR(...) fprintf(stderr, __VA_ARGS__)
/* Types & macros for XML/GraphML processing */
#define INDENT_STR " "
@@ -231,6 +226,7 @@
fprintf(stderr, "xml close element error %d at %s:%d", res, __FILE__, __LINE__); \
return CYBERIADA_XML_ERROR; \
}
# define XML_READMEMORY_BASENAME "noname.xml"
typedef struct {
char* attr_id;
@@ -438,97 +434,6 @@ static CyberiadaLink* cyberiada_copy_link(CyberiadaLink* src)
return cyberiada_new_link(src->ref);
}
CyberiadaPoint* cyberiada_new_point(void)
{
CyberiadaPoint* p = (CyberiadaPoint*)malloc(sizeof(CyberiadaPoint));
memset(p, 0, sizeof(CyberiadaPoint));
return p;
}
static CyberiadaPoint* cyberiada_copy_point(CyberiadaPoint* src)
{
CyberiadaPoint* dst;
if (!src) {
return NULL;
}
dst = cyberiada_new_point();
dst->x = src->x;
dst->y = src->y;
return dst;
}
static void cyberiada_update_point(CyberiadaPoint* p, float dx, float dy)
{
if (!p) return ;
p->x += dx;
p->y += dy;
}
CyberiadaRect* cyberiada_new_rect(void)
{
CyberiadaRect* r = (CyberiadaRect*)malloc(sizeof(CyberiadaRect));
memset(r, 0, sizeof(CyberiadaRect));
return r;
}
static CyberiadaRect* cyberiada_copy_rect(CyberiadaRect* src)
{
CyberiadaRect* dst;
if (!src) {
return NULL;
}
dst = cyberiada_new_rect();
dst->x = src->x;
dst->y = src->y;
dst->width = src->width;
dst->height = src->height;
return dst;
}
static void cyberiada_update_rect(CyberiadaRect* r, float dx, float dy)
{
if (!r) return ;
r->x += dx;
r->y += dy;
}
CyberiadaPolyline* cyberiada_new_polyline(void)
{
CyberiadaPolyline* pl = (CyberiadaPolyline*)malloc(sizeof(CyberiadaPolyline));
memset(pl, 0, sizeof(CyberiadaPolyline));
return pl;
}
static CyberiadaPolyline* cyberiada_copy_polyline(CyberiadaPolyline* src)
{
CyberiadaPolyline *dst = NULL, *prev, *pl;
if (!src) {
return NULL;
}
do {
pl = cyberiada_new_polyline();
pl->point.x = src->point.x;
pl->point.y = src->point.y;
if (dst) {
prev->next = pl;
} else {
dst = pl;
}
prev = pl;
src = src->next;
} while (src);
return dst;
}
/*static void cyberiada_update_polyline(CyberiadaPolyline* pl, float dx, float dy)
{
if (!pl) return ;
do {
cyberiada_update_point(&(pl->point), dx, dy);
pl = pl->next;
} while (pl);
}*/
CyberiadaAction* cyberiada_new_action(CyberiadaActionType type,
const char* trigger,
const char* guard,
@@ -545,21 +450,10 @@ CyberiadaAction* cyberiada_new_action(CyberiadaActionType type,
static CyberiadaAction* cyberiada_copy_action(CyberiadaAction* src)
{
CyberiadaAction *dst = NULL, *prev, *a;
if (!src) {
return NULL;
}
do {
a = cyberiada_new_action(src->type, src->trigger, src->guard, src->behavior);
if (dst) {
prev->next = a;
} else {
dst = a;
}
prev = a;
src = src->next;
} while (src);
return dst;
return cyberiada_new_action(src->type, src->trigger, src->guard, src->behavior);
}
CyberiadaNode* cyberiada_new_node(const char* id)
@@ -1256,7 +1150,6 @@ static CyberiadaEdge* cyberiada_graph_find_last_edge(CyberiadaSM* sm)
static int cyberiada_destroy_edge(CyberiadaEdge* e)
{
CyberiadaPolyline *polyline, *pl;
if (e->id) free(e->id);
if (e->source_id) free(e->source_id);
if (e->target_id) free(e->target_id);
@@ -1266,12 +1159,7 @@ static int cyberiada_destroy_edge(CyberiadaEdge* e)
free(e->comment_subject);
}
if (e->geometry_polyline) {
polyline = e->geometry_polyline;
do {
pl = polyline;
polyline = polyline->next;
free(pl);
} while (polyline);
cyberiada_destroy_polyline(e->geometry_polyline);
}
if (e->geometry_source_point) free(e->geometry_source_point);
if (e->geometry_target_point) free(e->geometry_target_point);
@@ -1311,7 +1199,7 @@ static void cyberiada_free_name_list(NamesList** nl)
cyberiada_list_free(nl);
}
static int cyberiada_graphs_reconstruct_nodes(CyberiadaNode* root, NamesList** nl)
static int cyberiada_graphs_reconstruct_node_identifiers(CyberiadaNode* root, NamesList** nl)
{
char buffer[MAX_STR_LEN];
size_t buffer_len = sizeof(buffer) - 1;
@@ -1340,14 +1228,14 @@ static int cyberiada_graphs_reconstruct_nodes(CyberiadaNode* root, NamesList** n
cyberiada_add_name_to_list(nl, "", node->id);
}
if (node->children) {
cyberiada_graphs_reconstruct_nodes(node->children, nl);
cyberiada_graphs_reconstruct_node_identifiers(node->children, nl);
}
node = node->next;
}
return CYBERIADA_NO_ERROR;
}
static int cyberiada_graphs_reconstruct_edges(CyberiadaDocument* doc, NamesList** nl)
static int cyberiada_graphs_reconstruct_edge_identifiers(CyberiadaDocument* doc, NamesList** nl)
{
char buffer[MAX_STR_LEN];
size_t buffer_len = sizeof(buffer) - 1;
@@ -1410,249 +1298,6 @@ static int cyberiada_graphs_reconstruct_edges(CyberiadaDocument* doc, NamesList*
return CYBERIADA_NO_ERROR;
}
static int cyberiada_graphs_reconstruct_node_geometry_from_yed(CyberiadaNode* node, float parent_x, float parent_y)
{
float new_root_x;
float new_root_y;
while (node) {
if (node->geometry_point) {
cyberiada_update_point(node->geometry_point, -parent_x, -parent_y);
}
if (node->geometry_rect) {
new_root_x = node->geometry_rect->x;
new_root_y = node->geometry_rect->y;
cyberiada_update_rect(node->geometry_rect, -parent_x, -parent_y);
} else {
new_root_x = parent_x;
new_root_y = parent_y;
}
if (node->children) {
cyberiada_graphs_reconstruct_node_geometry_from_yed(node->children, new_root_x, new_root_y);
}
node = node->next;
}
return CYBERIADA_NO_ERROR;
}
static int cyberiada_graphs_reconstruct_geometry_from_yed(CyberiadaDocument* doc)
{
CyberiadaSM* sm;
CyberiadaNode* node;
CyberiadaEdge* edge;
CyberiadaPolyline* pl;
float from_x, from_y, to_x, to_y;
float src_from_x, src_from_y, src_to_x, src_to_y, tgt_from_x, tgt_from_y, tgt_to_x, tgt_to_y;
for (sm = doc->state_machines; sm; sm = sm->next) {
for (edge = sm->edges; edge; edge = edge->next) {
if (edge->source && (edge->source->geometry_rect || edge->source->geometry_point) &&
edge->target && (edge->target->geometry_rect || edge->target->geometry_point) &&
(edge->geometry_source_point ||
edge->geometry_target_point ||
edge->geometry_polyline ||
edge->geometry_label_point)) {
from_x = from_y = to_x = to_y = 0.0f;
if (edge->source->geometry_point) {
from_x += edge->source->geometry_point->x;
from_y += edge->source->geometry_point->y;
} else {
from_x += edge->source->geometry_rect->x + edge->source->geometry_rect->width / 2.0f;
from_y += edge->source->geometry_rect->y + edge->source->geometry_rect->height / 2.0f;
if (edge->geometry_source_point) {
from_x += edge->geometry_source_point->x;
from_y += edge->geometry_source_point->y;
}
}
if (edge->target->geometry_point) {
to_x += edge->target->geometry_point->x;
to_y += edge->target->geometry_point->y;
} else {
to_x += edge->target->geometry_rect->x + edge->target->geometry_rect->width / 2.0f;
to_y += edge->target->geometry_rect->y + edge->target->geometry_rect->height / 2.0f;
if (edge->geometry_target_point) {
to_x += edge->geometry_target_point->x;
to_y += edge->geometry_target_point->y;
}
}
if (edge->geometry_polyline) {
float first_p_x = edge->geometry_polyline->point.x;
float first_p_y = edge->geometry_polyline->point.y;
float last_p_x;
float last_p_y;
pl = edge->geometry_polyline;
while (pl->next) pl = pl->next;
last_p_x = pl->point.x;
last_p_y = pl->point.y;
src_from_x = from_x;
src_from_y = from_y;
src_to_x = first_p_x;
src_to_y = first_p_y;
tgt_from_x = last_p_x;
tgt_from_y = last_p_y;
tgt_to_x = to_x;
tgt_to_y = to_y;
} else {
src_from_x = tgt_from_x = from_x;
src_from_y = tgt_from_y = from_y;
src_to_x = tgt_to_x = to_x;
src_to_y = tgt_to_y = to_y;
}
/* DEBUG("edge (%f; %f) -> (%f; %f) and (%f; %f) -> (%f; %f)\n", */
/* src_from_x, src_from_y, src_to_x, src_to_y, */
/* tgt_from_x, tgt_from_y, tgt_to_x, tgt_to_y); */
if (edge->geometry_source_point) {
if (edge->source->geometry_point) {
if (src_from_x == src_to_x) {
edge->geometry_source_point->x = 0.0f;
if (src_from_y >= src_to_y) {
edge->geometry_source_point->y = -PSEUDO_NODE_SIZE / 2.0f;
} else {
edge->geometry_source_point->y = PSEUDO_NODE_SIZE / 2.0f;
}
} else {
float alpha = (float)atan((src_from_y - src_to_y) / (src_to_x - src_from_x));
edge->geometry_source_point->x = (float)cos(alpha) * PSEUDO_NODE_SIZE / 2.0f;
edge->geometry_source_point->y = -(float)sin(alpha) * PSEUDO_NODE_SIZE / 2.0f;
}
} else {
if (src_from_x == src_to_x) {
edge->geometry_source_point->x = edge->source->geometry_rect->width / 2.0f;
if (src_from_y >= src_to_y) {
edge->geometry_source_point->y = 0.0f;
} else {
edge->geometry_source_point->y = edge->source->geometry_rect->height;
}
} else {
float alpha = (float)atan((src_from_y - src_to_y) / (src_to_x - src_from_x));
if (src_to_x < src_from_x) alpha += (float)M_PI;
float alpha_g = 180.0f * alpha / (float)M_PI;
/* DEBUG("src alpha %f\n", alpha_g);*/
if (alpha_g < 0.0f) alpha_g += 360.0f;
if (alpha_g <= 45.0f || alpha_g > 315.0f) {
edge->geometry_source_point->x = edge->source->geometry_rect->width;
edge->geometry_source_point->y += (-(float)tan(alpha) * edge->source->geometry_rect->width +
edge->source->geometry_rect->height) / 2.0f;
} else if (alpha_g > 45.0f && alpha_g <= 135.0f) {
edge->geometry_source_point->x += ((float)tan(alpha) * edge->source->geometry_rect->height +
edge->source->geometry_rect->width) / 2.0f;
edge->geometry_source_point->y = 0.0f;
} else if (alpha_g > 135.0f && alpha_g <= 225.0f) {
edge->geometry_source_point->x = 0.0f;
edge->geometry_source_point->y += (-(float)tan(alpha) * edge->source->geometry_rect->width +
edge->source->geometry_rect->height) / 2.0f;
} else {
edge->geometry_source_point->x += (-(float)tan(alpha) * edge->source->geometry_rect->height +
edge->source->geometry_rect->width) / 2.0f;
edge->geometry_source_point->y = edge->source->geometry_rect->height;
}
if (edge->geometry_source_point->x < 0) {
edge->geometry_source_point->x = 0.0f;
}
if (edge->geometry_source_point->x > edge->source->geometry_rect->width) {
edge->geometry_source_point->x = edge->source->geometry_rect->width;
}
if (edge->geometry_source_point->y < 0) {
edge->geometry_source_point->y = 0.0f;
}
if (edge->geometry_source_point->y > edge->source->geometry_rect->height) {
edge->geometry_source_point->y = edge->source->geometry_rect->height;
}
}
}
/* DEBUG("sp: (%f; %f)\n", edge->geometry_source_point->x, edge->geometry_source_point->y); */
}
if (edge->geometry_target_point) {
if (edge->target->geometry_point) {
if (tgt_from_x == tgt_to_x) {
edge->geometry_target_point->x = 0.0f;
if (tgt_from_y >= tgt_to_y) {
edge->geometry_target_point->y = PSEUDO_NODE_SIZE / 2.0f;
} else {
edge->geometry_target_point->y = -PSEUDO_NODE_SIZE / 2.0f;
}
} else {
float alpha = (float)atan((tgt_from_y - tgt_to_y) / (tgt_to_x - tgt_from_x));
edge->geometry_target_point->x = (float)cos(alpha) * PSEUDO_NODE_SIZE / 2.0f;
edge->geometry_target_point->y = -(float)sin(alpha) * PSEUDO_NODE_SIZE / 2.0f;
}
} else {
if (tgt_from_x == tgt_to_x) {
edge->geometry_target_point->x = edge->target->geometry_rect->width / 2.0f;
if (tgt_from_y >= tgt_to_y) {
edge->geometry_target_point->y = edge->target->geometry_rect->height;
} else {
edge->geometry_target_point->y = 0.0f;
}
} else {
float alpha = (float)atan((tgt_from_y - tgt_to_y) / (tgt_to_x - tgt_from_x));
if (tgt_to_x < tgt_from_x) alpha += (float)M_PI;
alpha += (float)M_PI; /* target = incoming edge */
float alpha_g = 180.0f * alpha / (float)M_PI;
/* DEBUG("tgt alpha %f\n", alpha_g);*/
if (alpha_g < 0.0f) alpha_g += 360.0f;
if (alpha_g <= 45.0f || alpha_g > 315.0f) {
edge->geometry_target_point->x = edge->target->geometry_rect->width;
edge->geometry_target_point->y += (-(float)tan(alpha) * edge->target->geometry_rect->width +
edge->target->geometry_rect->height) / 2.0f;
} else if (alpha_g > 45.0f && alpha_g <= 135.0f) {
edge->geometry_target_point->x += ((float)tan(alpha) * edge->target->geometry_rect->height +
edge->target->geometry_rect->width) / 2.0f;
edge->geometry_target_point->y = 0.0f;
} else if (alpha_g > 135.0f && alpha_g <= 225.0f) {
edge->geometry_target_point->x = 0.0f;
edge->geometry_target_point->y += (-(float)tan(alpha) * edge->target->geometry_rect->width +
edge->target->geometry_rect->height) / 2.0f;
} else {
edge->geometry_target_point->x += (-(float)tan(alpha) * edge->target->geometry_rect->height +
edge->target->geometry_rect->width) / 2.0f;
edge->geometry_target_point->y = edge->target->geometry_rect->height;
}
}
if (edge->geometry_target_point->x < 0) {
edge->geometry_target_point->x = 0.0f;
}
if (edge->geometry_target_point->x > edge->target->geometry_rect->width) {
edge->geometry_target_point->x = edge->target->geometry_rect->width;
}
if (edge->geometry_target_point->y < 0) {
edge->geometry_target_point->y = 0.0f;
}
if (edge->geometry_target_point->y > edge->target->geometry_rect->height) {
edge->geometry_target_point->y = edge->target->geometry_rect->height;
}
}
/* DEBUG("tp: (%f; %f)\n", edge->geometry_target_point->x, edge->geometry_target_point->y); */
}
}
}
for (node = sm->nodes; node; node = node->next) {
cyberiada_graphs_reconstruct_node_geometry_from_yed(node, 0.0f, 0.0f);
}
}
return CYBERIADA_NO_ERROR;
}
static int cyberiada_graphs_reconstruct_geometry_to_yed(CyberiadaDocument* doc)
{
return CYBERIADA_NO_ERROR;
}
typedef struct {
const char* name;
size_t value_offset;
@@ -1961,20 +1606,10 @@ static int cyberiada_decode_meta(CyberiadaDocument* doc, char* metadata)
return CYBERIADA_NO_ERROR;
}
static int cyberiada_init_sm(CyberiadaSM* sm)
{
if (sm) {
sm->nodes = NULL;
sm->edges = NULL;
sm->next = NULL;
}
return CYBERIADA_NO_ERROR;
}
CyberiadaSM* cyberiada_new_sm(void)
{
CyberiadaSM* sm = (CyberiadaSM*)malloc(sizeof(CyberiadaSM));
cyberiada_init_sm(sm);
memset(sm, 0, sizeof(CyberiadaSM));
return sm;
}
@@ -1982,6 +1617,9 @@ static int cyberiada_destroy_sm(CyberiadaSM* sm)
{
CyberiadaEdge *edge, *e;
if (sm) {
if (sm->bounding_rect) {
free(sm->bounding_rect);
}
if (sm->nodes) {
cyberiada_destroy_all_nodes(sm->nodes);
}
@@ -2266,7 +1904,7 @@ static int cyberiada_get_element_text(char* buffer, size_t buffer_len,
static int cyberiada_xml_read_coord(xmlNode* xml_node,
const char* attr_name,
float* result)
double* result)
{
char buffer[MAX_STR_LEN];
size_t buffer_len = sizeof(buffer) - 1;
@@ -2275,7 +1913,7 @@ static int cyberiada_xml_read_coord(xmlNode* xml_node,
attr_name) != CYBERIADA_NO_ERROR) {
return CYBERIADA_BAD_PARAMETER;
}
*result = (float)atof(buffer);
*result = (double)atof(buffer);
return CYBERIADA_NO_ERROR;
}
@@ -2528,8 +2166,8 @@ static GraphProcessorState handle_node_geometry(xmlNode* xml_node,
}
if (type == cybNodeInitial) {
current->geometry_point = cyberiada_new_point();
current->geometry_point->x = rect->x + rect->width / 2.0f;
current->geometry_point->y = rect->y + rect->height / 2.0f;
current->geometry_point->x = rect->x + rect->width / 2.0;
current->geometry_point->y = rect->y + rect->height / 2.0;
free(rect);
return gpsNodeStart;
} else if (type == cybNodeComment) {
@@ -2574,8 +2212,10 @@ static GraphProcessorState handle_node_title(xmlNode* xml_node,
return gpsInvalid;
}
cyberiada_get_element_text(buffer, buffer_len, xml_node);
/* DEBUG("Set node %s title %s\n", current->id, buffer); */
DEBUG("Set node %s title '%s'\n", current->id, buffer);
cyberiada_copy_string(&(current->title), &(current->title_len), buffer);
cyberiada_string_trim(current->title);
DEBUG("After trim: '%s'\n", current->title);
return gpsNodeAction;
}
@@ -2658,7 +2298,7 @@ static GraphProcessorState handle_edge_label(xmlNode* xml_node,
{
char buffer[MAX_STR_LEN];
size_t buffer_len = sizeof(buffer) - 1;
float x = 0.0, y = 0.0;
double x = 0.0, y = 0.0;
CyberiadaEdge *current;
CyberiadaSM* sm = doc->state_machines;
while (sm->next) sm = sm->next;
@@ -2870,6 +2510,7 @@ static GraphProcessorState handle_node_data(xmlNode* xml_node,
}
/* DEBUG("Set node %s title %s\n", current->id, buffer); */
cyberiada_copy_string(&(current->title), &(current->title_len), buffer);
cyberiada_string_trim(current->title);
} else if (strcmp(key_name, GRAPHML_CYB_KEY_STATE_MACHINE_NAME) == 0) {
if (current->type != cybNodeSM) {
ERROR("Using state machine key outside the graph element in %s\n", current->id);
@@ -3466,40 +3107,74 @@ static int cyberiada_check_graphml_ns(xmlNode* root, CyberiadaXMLFormat* format)
/* -----------------------------------------------------------------------------
* GraphML reader interface
* ----------------------------------------------------------------------------- */
int cyberiada_read_sm_document(CyberiadaDocument* cyb_doc, const char* filename, CyberiadaXMLFormat format)
static int cyberiada_process_decode_sm_document(CyberiadaDocument* cyb_doc, xmlDoc* doc, CyberiadaXMLFormat format, int flags)
{
int res;
xmlDoc* doc = NULL;
xmlNode* root = NULL;
CyberiadaSM* sm;
NamesList* nl = NULL;
int geom_flags;
if (flags & CYBERIADA_FLAG_ROUND_GEOMETRY) {
ERROR("Round geometry flag is not supported on import\n");
return CYBERIADA_BAD_PARAMETER;
}
if (flags & CYBERIADA_FLAG_SKIP_GEOMETRY &&
flags != CYBERIADA_FLAG_SKIP_GEOMETRY) {
ERROR("The skip geometry flag is not compatible with other flags\n");
return CYBERIADA_BAD_PARAMETER;
}
geom_flags = flags & (CYBERIADA_FLAG_ABSOLUTE_GEOMETRY |
CYBERIADA_FLAG_LEFTTOP_LOCAL_GEOMETRY |
CYBERIADA_FLAG_CENTER_LOCAL_GEOMETRY);
if (geom_flags &&
geom_flags != CYBERIADA_FLAG_ABSOLUTE_GEOMETRY &&
geom_flags != CYBERIADA_FLAG_LEFTTOP_LOCAL_GEOMETRY &&
geom_flags != CYBERIADA_FLAG_CENTER_LOCAL_GEOMETRY) {
ERROR("Single geometry flag (abs, left-top, center) can be used at the same time\n");
return CYBERIADA_BAD_PARAMETER;
}
if (!(flags & CYBERIADA_FLAG_SKIP_GEOMETRY) && !geom_flags) {
/* set default geometry flag */
flags |= CYBERIADA_FLAG_CENTER_LOCAL_GEOMETRY;
}
geom_flags = flags & (CYBERIADA_FLAG_CENTER_EDGE_GEOMETRY |
CYBERIADA_FLAG_LEFTTOP_BORDER_EDGE_GEOMETRY |
CYBERIADA_FLAG_CENTER_BORDER_EDGE_GEOMETRY);
if (geom_flags &&
geom_flags != CYBERIADA_FLAG_CENTER_EDGE_GEOMETRY &&
geom_flags != CYBERIADA_FLAG_LEFTTOP_BORDER_EDGE_GEOMETRY &&
geom_flags != CYBERIADA_FLAG_CENTER_BORDER_EDGE_GEOMETRY) {
ERROR("Single geometry edge flag (border, center) can be used at the same time\n");
return CYBERIADA_BAD_PARAMETER;
}
if (!(flags & CYBERIADA_FLAG_SKIP_GEOMETRY) && !geom_flags) {
/* set default edge geometry flag */
flags |= CYBERIADA_FLAG_CENTER_BORDER_EDGE_GEOMETRY;
}
cyberiada_init_sm_document(cyb_doc);
cyberiada_init_action_regexps();
xmlInitParser();
do {
/* parse the file and get the DOM */
if ((doc = xmlReadFile(filename, NULL, 0)) == NULL) {
ERROR("error: could not parse file %s\n", filename);
res = CYBERIADA_XML_ERROR;
break;
}
/* get the root element node */
root = xmlDocGetRootElement(doc);
if (strcmp((const char*)root->name, GRAPHML_GRAPHML_ELEMENT) != 0) {
ERROR("error: could not find GraphML root node %s\n", filename);
ERROR("error: could not find GraphML root node\n");
res = CYBERIADA_XML_ERROR;
break;
}
/* check whether the xml is graphml */
if (cyberiada_check_graphml_ns(root, &format)) {
ERROR("error: no valid graphml namespace in %s\n", filename);
ERROR("error: no valid graphml namespace\n");
res = CYBERIADA_XML_ERROR;
break;
}
@@ -3512,8 +3187,7 @@ int cyberiada_read_sm_document(CyberiadaDocument* cyb_doc, const char* filename,
} else if (format == cybxmlCyberiada10) {
res = cyberiada_decode_cyberiada_xml(root, cyb_doc);
} else {
ERROR("error: unsupported GraphML format of file %s\n",
filename);
ERROR("error: unsupported GraphML format of file\n");
res = CYBERIADA_XML_ERROR;
break;
}
@@ -3523,33 +3197,79 @@ int cyberiada_read_sm_document(CyberiadaDocument* cyb_doc, const char* filename,
}
for (sm = cyb_doc->state_machines; sm; sm = sm->next) {
if ((res = cyberiada_graphs_reconstruct_nodes(sm->nodes, &nl)) != CYBERIADA_NO_ERROR) {
ERROR("error: cannot reconstruct graph nodes file %s\n",
filename);
if ((res = cyberiada_graphs_reconstruct_node_identifiers(sm->nodes, &nl)) != CYBERIADA_NO_ERROR) {
ERROR("error: cannot reconstruct graph nodes' indentifiers\n");
break;
}
}
if ((res = cyberiada_graphs_reconstruct_edges(cyb_doc, &nl)) != CYBERIADA_NO_ERROR) {
ERROR("error: cannot reconstruct graph edges from file %s\n",
filename);
if ((res = cyberiada_graphs_reconstruct_edge_identifiers(cyb_doc, &nl)) != CYBERIADA_NO_ERROR) {
ERROR("error: cannot reconstruct graph edges' identifier\n");
break;
}
if (format == cybxmlYED && res == CYBERIADA_NO_ERROR) {
cyberiada_graphs_reconstruct_geometry_from_yed(cyb_doc);
if (res == CYBERIADA_NO_ERROR) {
if (flags & CYBERIADA_FLAG_SKIP_GEOMETRY) {
cyberiada_clean_document_geometry(cyb_doc);
} else {
cyberiada_import_document_geometry(cyb_doc, flags, format);
}
}
} while(0);
cyberiada_free_name_list(&nl);
cyberiada_free_action_regexps();
xmlFreeDoc(doc);
xmlCleanupParser();
return res;
}
int cyberiada_read_sm_document(CyberiadaDocument* cyb_doc, const char* filename,
CyberiadaXMLFormat format, int flags)
{
int res;
xmlDoc* doc = NULL;
xmlInitParser();
/* parse the file and get the DOM */
if ((doc = xmlReadFile(filename, NULL, 0)) == NULL) {
ERROR("error: could not parse file %s\n", filename);
xmlCleanupParser();
return CYBERIADA_XML_ERROR;
}
res = cyberiada_process_decode_sm_document(cyb_doc, doc, format, flags);
if (doc) {
xmlFreeDoc(doc);
}
xmlCleanupParser();
return res;
}
int cyberiada_decode_sm_document(CyberiadaDocument* cyb_doc, const char* buffer, size_t buffer_size,
CyberiadaXMLFormat format, int flags)
{
int res;
xmlDoc* doc = NULL;
xmlInitParser();
/* parse the file and get the DOM */
if ((doc = xmlReadMemory(buffer, buffer_size, XML_READMEMORY_BASENAME, NULL, 0)) == NULL) {
ERROR("error: could not read buffer\n");
xmlCleanupParser();
return CYBERIADA_XML_ERROR;
}
res = cyberiada_process_decode_sm_document(cyb_doc, doc, format, flags);
if (doc) {
xmlFreeDoc(doc);
}
xmlCleanupParser();
return res;
}
/* -----------------------------------------------------------------------------
* Printing functions
* ----------------------------------------------------------------------------- */
@@ -4695,12 +4415,39 @@ static int cyberiada_write_sm_document_yed(CyberiadaDocument* doc, xmlTextWriter
* GraphML writer interface
* ----------------------------------------------------------------------------- */
int cyberiada_write_sm_document(CyberiadaDocument* doc, const char* filename, CyberiadaXMLFormat format)
static int cyberiada_process_encode_sm_document(CyberiadaDocument* doc, xmlTextWriterPtr writer,
CyberiadaXMLFormat format, int flags)
{
CyberiadaDocument* copy_doc = NULL;
xmlTextWriterPtr writer;
int res;
if (flags & CYBERIADA_FLAG_RECONSTRUCT_GEOMETRY) {
ERROR("Geometry reconstructioin flag is not supported on export\n");
return CYBERIADA_BAD_PARAMETER;
}
if (flags & CYBERIADA_FLAG_SKIP_GEOMETRY) {
if (flags != CYBERIADA_FLAG_SKIP_GEOMETRY) {
ERROR("The skip geometry flag is not compatible with other flags\n");
return CYBERIADA_BAD_PARAMETER;
}
if (format == cybxmlYED) {
ERROR("Skip geometry flag is not allowed for YED export\n");
return CYBERIADA_BAD_PARAMETER;
}
}
if (CYBERIADA_FLAG_ABSOLUTE_GEOMETRY |
CYBERIADA_FLAG_LEFTTOP_LOCAL_GEOMETRY |
CYBERIADA_FLAG_CENTER_LOCAL_GEOMETRY |
CYBERIADA_FLAG_LEFTTOP_BORDER_EDGE_GEOMETRY |
CYBERIADA_FLAG_CENTER_BORDER_EDGE_GEOMETRY |
CYBERIADA_FLAG_CENTER_EDGE_GEOMETRY) {
ERROR("Geometry flags (abs, left-top, center) & edge geometry flags are not allowed for export\n");
return CYBERIADA_BAD_PARAMETER;
}
if (format != cybxmlCyberiada10 && format != cybxmlYED) {
ERROR("unsupported SM format for write: %d\n", format);
return CYBERIADA_BAD_PARAMETER;
@@ -4711,16 +4458,12 @@ int cyberiada_write_sm_document(CyberiadaDocument* doc, const char* filename, Cy
return CYBERIADA_BAD_PARAMETER;
}
xmlInitParser();
do {
writer = xmlNewTextWriterFilename(filename, 0);
if (!writer) {
ERROR("cannot open xml writter for file %s\n", filename);
res = CYBERIADA_XML_ERROR;
if (format == cybxmlYED && (!doc->state_machines ||doc->state_machines->next)) {
ERROR("YED format supports only single SM documents\n");
return CYBERIADA_BAD_PARAMETER;
}
do {
res = xmlTextWriterStartDocument(writer, NULL, GRAPHML_XML_ENCODING, NULL);
if (res < 0) {
ERROR("error writing xml start document: %d\n", res);
@@ -4728,22 +4471,20 @@ int cyberiada_write_sm_document(CyberiadaDocument* doc, const char* filename, Cy
break;
}
if (format == cybxmlYED) {
if (!doc->state_machines || doc->state_machines->next) {
ERROR("YED format supports only single SM documents\n");
res = CYBERIADA_BAD_PARAMETER;
break;
}
copy_doc = cyberiada_copy_sm_document(doc);
cyberiada_graphs_reconstruct_geometry_to_yed(copy_doc);
res = cyberiada_write_sm_document_yed(copy_doc, writer);
cyberiada_destroy_sm_document(copy_doc);
} else if (format == cybxmlCyberiada10) {
res = cyberiada_write_sm_document_cyberiada(doc, writer);
if (flags & CYBERIADA_FLAG_SKIP_GEOMETRY) {
cyberiada_clean_document_geometry(copy_doc);
} else {
cyberiada_export_document_geometry(copy_doc, flags, format);
}
if (format == cybxmlYED) {
res = cyberiada_write_sm_document_yed(copy_doc, writer);
} else if (format == cybxmlCyberiada10) {
res = cyberiada_write_sm_document_cyberiada(copy_doc, writer);
}
cyberiada_destroy_sm_document(copy_doc);
if (res != CYBERIADA_NO_ERROR) {
ERROR("error writing xml %d\n", res);
res = CYBERIADA_XML_ERROR;
@@ -4760,12 +4501,72 @@ int cyberiada_write_sm_document(CyberiadaDocument* doc, const char* filename, Cy
res = CYBERIADA_NO_ERROR;
} while (0);
xmlFreeTextWriter(writer);
xmlCleanupParser();
if (res != CYBERIADA_NO_ERROR) {
return res;
} else {
return CYBERIADA_NO_ERROR;
}
}
int cyberiada_write_sm_document(CyberiadaDocument* doc, const char* filename,
CyberiadaXMLFormat format, int flags)
{
int res;
xmlTextWriterPtr writer = NULL;
xmlInitParser();
writer = xmlNewTextWriterFilename(filename, 0);
if (!writer) {
ERROR("cannot open xml writter for file %s\n", filename);
xmlCleanupParser();
return CYBERIADA_XML_ERROR;
}
res = cyberiada_process_encode_sm_document(doc, writer, format, flags);
xmlFreeTextWriter(writer);
xmlCleanupParser();
return res;
}
int cyberiada_encode_sm_document(CyberiadaDocument* doc, char** buffer, size_t* buffer_size,
CyberiadaXMLFormat format, int flags)
{
int res;
xmlBufferPtr xml_buffer;
xmlTextWriterPtr writer = NULL;
xmlInitParser();
xml_buffer = xmlBufferCreate();
if (!xml_buffer) {
ERROR("cannot create xml buffer\n");
xmlCleanupParser();
return CYBERIADA_XML_ERROR;
}
writer = xmlNewTextWriterMemory(xml_buffer, 0);
if (!writer) {
ERROR("cannot create buffer writter\n");
xmlBufferFree(xml_buffer);
xmlCleanupParser();
return CYBERIADA_XML_ERROR;
}
res = cyberiada_process_encode_sm_document(doc, writer, format, flags);
if (res == CYBERIADA_NO_ERROR) {
size_t size = xml_buffer->use;
*buffer = (char*)malloc(size + 1);
memcpy(*buffer, xml_buffer->content, size);
(*buffer)[size] = 0;
*buffer_size = size;
} else {
*buffer = NULL;
*buffer_size = 0;
}
xmlBufferFree(xml_buffer);
xmlFreeTextWriter(writer);
xmlCleanupParser();
return res;
}

View File

@@ -70,11 +70,11 @@ typedef enum {
/* SM node & transitions geometry */
typedef struct {
float x, y;
double x, y;
} CyberiadaPoint;
typedef struct {
float x, y, width, height;
double x, y, width, height;
} CyberiadaRect;
typedef struct _CyberiadaPolyline {
@@ -108,14 +108,26 @@ typedef struct _CyberiadaLink {
size_t ref_len;
} CyberiadaLink;
/* SM node (state) */
/* SM node (state)
*
* Comment on the node geometry:
* - if the top-level node (state machine) has geometry, it is calculated in
* absolute coordinates (x: left-to-right, y: top-to-bottom);
* - if the state machine has no geometry, the first level nodes (comporite states, initial
* vertexes, etc.) are calculated in absolute coordinates;
* - the child nodes are calculated in local coordinates relative to the _center_ of the
* parent node.
*
* NOTE: this geomerty is different from the Cyberiada GraphML 1.0 / YED formats geometry
* and is being converted during the document import/export.
*/
typedef struct _CyberiadaNode {
CyberiadaNodeType type;
char* id;
size_t id_len;
char* title;
size_t title_len;
CyberiadaAction* actions; /* for simple & composite nodes */
CyberiadaAction* actions; /* for simple & composite state nodes */
CyberiadaCommentData* comment_data; /* for comments */
CyberiadaLink* link; /* for submachine states */
CyberiadaPoint* geometry_point; /* for some pseudostates & final state */
@@ -139,7 +151,19 @@ typedef struct _CyberiadaCommentSubject {
size_t fragment_len;
} CyberiadaCommentSubject;
/* SM edge (transition) */
/* SM edge (transition)
*
* Comment on the edge geometry:
* - the source and target points are calculated in local coordinates relative
* to the center of the corresponding source/target node;
* - the label point is calculated in local coordinates relative to the geometrical
* center of the edge (based on the bounding rectangle);
* - the polyline points are calculated in local coordinated relative to the center of
* the source node.
*
* NOTE: this geomerty is different from the Cyberiada GraphML 1.0 / YED formats geometry
* and is being converted during the document import/export.
*/
typedef struct _CyberiadaEdge {
CyberiadaEdgeType type;
char* id;
@@ -165,6 +189,7 @@ typedef struct _CyberiadaEdge {
typedef struct _CyberiadaSM {
CyberiadaNode* nodes;
CyberiadaEdge* edges;
CyberiadaRect* bounding_rect;
struct _CyberiadaSM* next;
} CyberiadaSM;
@@ -212,11 +237,29 @@ typedef struct {
size_t markup_language_len;
} CyberiadaMetainformation;
/* SM document node coordinates format */
typedef enum {
cybCoordNone = 0, /* no geometry information */
cybCoordAbsolute = 1, /* absolute coordinates (used in YED) */
cybCoordLeftTop = 2, /* left-top-oriented local coordinates (used in Cyberiada10) */
cybCoordLocalCenter = 4, /* center-oriented local coordinates (used by default) */
} CyberiadaGeometryCoordFormat;
/* SM document edges source/target point placement & coordinates format */
typedef enum {
cybEdgeNone = 0,
cybEdgeLocalCenter = 1, /* source & target points are bind to a node's center */
cybEdgeLeftTopBorder = 2, /* source & target points are placed on a node's border with left-top coordinates */
cybEdgeCenterBorder = 4, /* source & target points are placed on a node's border with center coordinates */
} CyberiadaGeometryEdgeFormat;
/* SM document */
typedef struct {
char* format; /* SM document format string */
char* format; /* SM document format string (additional info) */
size_t format_len; /* SM document format string length */
CyberiadaMetainformation* meta_info; /* SM document metainformation */
CyberiadaGeometryCoordFormat geometry_format; /* SM document geometry coordinates format */
CyberiadaGeometryEdgeFormat edge_geom_format; /* SM document edges geometry format */
CyberiadaSM* state_machines; /* State machines */
} CyberiadaDocument;
@@ -227,6 +270,18 @@ typedef enum {
cybxmlUnknown = 99 /* Format is not specified */
} CyberiadaXMLFormat;
/* Cyberiada GraphML Library import/export flags */
#define CYBERIADA_FLAG_NO 0
#define CYBERIADA_FLAG_ABSOLUTE_GEOMETRY 1 /* convert imported geometry to absolute coordinates */
#define CYBERIADA_FLAG_LEFTTOP_LOCAL_GEOMETRY 2 /* convert imported geometry to left-top-oriented local coordinates */
#define CYBERIADA_FLAG_CENTER_LOCAL_GEOMETRY 4 /* convert imported geometry to center-oriented local coordinates */
#define CYBERIADA_FLAG_CENTER_EDGE_GEOMETRY 8 /* convert imported geometry to center edge coordinates */
#define CYBERIADA_FLAG_LEFTTOP_BORDER_EDGE_GEOMETRY 16 /* convert imported geometry to border edge coordinates (left-top) */
#define CYBERIADA_FLAG_CENTER_BORDER_EDGE_GEOMETRY 32 /* convert imported geometry to border edge coordinates (center) */
#define CYBERIADA_FLAG_RECONSTRUCT_GEOMETRY 64 /* reconstruct absent node/edge geometry on import */
#define CYBERIADA_FLAG_SKIP_GEOMETRY 128 /* skip geometry node/edge during import/export */
#define CYBERIADA_FLAG_ROUND_GEOMETRY 256 /* export geometry with round coordinates to 0.001 */
/* -----------------------------------------------------------------------------
* The Cyberiada GraphML error codes
* ----------------------------------------------------------------------------- */
@@ -263,10 +318,19 @@ typedef enum {
/* Read an XML file and decode the SM structure */
/* Allocate the SM document structure first */
int cyberiada_read_sm_document(CyberiadaDocument* doc, const char* filename, CyberiadaXMLFormat format);
int cyberiada_read_sm_document(CyberiadaDocument* doc, const char* filename, CyberiadaXMLFormat format, int flags);
/* Encode the SM document structure and write the data to an XML file */
int cyberiada_write_sm_document(CyberiadaDocument* doc, const char* filename, CyberiadaXMLFormat format);
int cyberiada_write_sm_document(CyberiadaDocument* doc, const char* filename, CyberiadaXMLFormat format, int flags);
/* Decode the SM structure */
/* Allocate the SM document structure first */
int cyberiada_decode_sm_document(CyberiadaDocument* doc, const char* buffer, size_t buffer_size,
CyberiadaXMLFormat format, int flags);
/* Encode the SM document structure */
int cyberiada_encode_sm_document(CyberiadaDocument* doc, char** buffer, size_t* buffer_size,
CyberiadaXMLFormat format, int flags);
/* Print the SM structure to stdout */
int cyberiada_print_sm_document(CyberiadaDocument* doc);
@@ -301,6 +365,14 @@ typedef enum {
/* Allocate and initialize the SM polyline structure in memory */
CyberiadaPolyline* cyberiada_new_polyline(void);
/* Free the SM polyline structure in memory */
int cyberiada_destroy_polyline(CyberiadaPolyline* polyline);
/* Change the SM document geometry format and convert the SMs geometry data */
int cyberiada_convert_document_geometry(CyberiadaDocument* doc,
CyberiadaGeometryCoordFormat new_format,
CyberiadaGeometryEdgeFormat new_edge_format);
/* Allocate and initialize the SM metainformation structure in memory */
CyberiadaMetainformation* cyberiada_new_meta(void);

1209
geometry.c Normal file

File diff suppressed because it is too large Load Diff

59
geometry.h Normal file
View File

@@ -0,0 +1,59 @@
/* -----------------------------------------------------------------------------
* The Cyberiada GraphML library implemention
*
* The geometry utilities
*
* Copyright (C) 2024 Alexey Fedoseev <aleksey@fedoseev.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser 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
* Lesser 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 https://www.gnu.org/licenses/
*
* ----------------------------------------------------------------------------- */
#ifndef __CYBERIADA_GEOMETRY_H
#define __CYBERIADA_GEOMRTRY_H
#include "cyberiadaml.h"
#ifdef __cplusplus
extern "C" {
#endif
/* -----------------------------------------------------------------------------
* The Cyberiada GraphML geometry utilities
* ----------------------------------------------------------------------------- */
CyberiadaPoint* cyberiada_new_point(void);
CyberiadaPoint* cyberiada_copy_point(CyberiadaPoint* src);
CyberiadaRect* cyberiada_new_rect(void);
CyberiadaRect* cyberiada_copy_rect(CyberiadaRect* src);
CyberiadaPolyline* cyberiada_new_polyline(void);
CyberiadaPolyline* cyberiada_copy_polyline(CyberiadaPolyline* src);
int cyberiada_destroy_polyline(CyberiadaPolyline* polyline);
int cyberiada_clean_document_geometry(CyberiadaDocument* doc);
int cyberiada_convert_document_geometry(CyberiadaDocument* doc,
CyberiadaGeometryCoordFormat new_format,
CyberiadaGeometryEdgeFormat new_edge_format);
int cyberiada_import_document_geometry(CyberiadaDocument* doc,
int flags, CyberiadaXMLFormat file_format);
int cyberiada_export_document_geometry(CyberiadaDocument* doc,
int flags, CyberiadaXMLFormat file_format);
#ifdef __cplusplus
}
#endif
#endif