/*
* Copyright (c) 2017 Frank Fischer
*
* This file is part of KraView.
*
* KraView 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.
*
* KraView 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 KraView. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Net.hxx"
#include "Err.hxx"
#include <istream>
#include <vector>
#include <map>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#define xassert_node(nodeid) \
if (!(nodeid < d->nodes.size())) \
throw NetError(std::string("Invalid node id: ") + std::to_string(nodeid));
#define xassert_arc(arcid) \
if (!(/*(arcid) >= 0 &&*/ (arcid) < d->arcs.size())) \
throw NetError(std::string("Invalid arc id: ") + std::to_string(arcid));
namespace Grav
{
struct Net::Data {
std::vector<Node> nodes;
std::vector<Arc> arcs;
std::map<size_type, Net::NodeID> nodes_by_number;
};
Net::Net():
d(new Data)
{}
Net::Net(const Net& net) :
d(new Data(*net.d))
{}
Net::~Net() {}
size_type Net::n_nodes() const
{
return d->nodes.size();
}
Net::NodeID Net::add_node(size_type nodenumber, const Node& node)
{
NodeID nodeid = d->nodes.size();
auto ret = d->nodes_by_number.insert({nodenumber, nodeid});
if (ret.second) {
d->nodes.push_back(node);
} else {
nodeid = ret.first->second;
d->nodes[nodeid] = node;
}
return nodeid;
}
Node& Net::node(NodeID nodeid)
{
xassert_node(nodeid);
return d->nodes[nodeid];
}
Net::NodeID Net::nodeid(size_type nodenumber) const
{
auto it = d->nodes_by_number.find(nodenumber);
return it != d->nodes_by_number.end() ? it->second : None;
}
size_type Net::n_arcs() const
{
return d->arcs.size();
}
Net::ArcID Net::add_arc(const Arc& arc)
{
xassert_node(arc.src);
xassert_node(arc.snk);
d->arcs.push_back(arc);
return d->arcs.size() - 1;
}
Arc& Net::arc(ArcID arcid)
{
xassert_arc(arcid);
return d->arcs[arcid];
}
static void parse_uint(const char* line, unsigned& n)
{
if (sscanf(line, "%u", &n) != 1)
throw ReadError(std::string("Expected number, got '") + line + "'");
}
static void parse_dbl(const char* line, double& dbl)
{
if (sscanf(line, "%lf", &dbl) != 1)
throw ReadError(std::string("Expected number, got '") + line + "'");
}
static void parse_color(const char* line, Color& color)
{
unsigned r, g, b;
double alpha;
if (sscanf(line, "%u,%u,%u,%lg", &r, &g, &b, &alpha) == 4) {
if (alpha < 1e-6 || alpha > 1.0 + 1e-6) {
throw ReadError(std::string("Alpha value outside [0,1], got: '")
+ std::to_string(alpha) + "'");
}
color = Color {
(unsigned char)r,
(unsigned char)g,
(unsigned char)b,
std::max(std::min(alpha, 1.0), 0.0)
};
} else if (sscanf(line, "%u,%u,%u", &r, &g, &b) == 3) {
color = Color {
(unsigned char)r,
(unsigned char)g,
(unsigned char)b,
1.0
};
} else {
throw ReadError(std::string("Invalid RGB color: ") + line);
}
}
static void parse_node_options(Node& node, const char* line, unsigned& desclen)
{
char option[11], sep[2];
int len, optpos;
desclen = 0;
for (;;) {
while (isspace(*line)) line++;
if (*line == '\0') break;
if (sscanf(line, "%10[a-z]%1[:]%n%*s%n",
option, sep, &optpos, &len) >= 2) {
/*
* an option with parameter
*/
if (std::strcmp(option, "x") == 0) {
parse_dbl(line + optpos, node.x);
} else if (strcmp(option, "y") == 0) {
parse_dbl(line + optpos, node.y);
} else if (strcmp(option, "weight") == 0) {
parse_dbl(line + optpos, node.weight);
} else if (strcmp(option, "color") == 0) {
parse_color(line + optpos, node.color);
} else if (strcmp(option, "desc") == 0) {
parse_uint(line + optpos, desclen);
} else {
throw ReadError(std::string("Unknown node option: ") + option);
}
} else if (sscanf(line, "%10s %n", option, &len) >= 1) {
if (strcmp(option, "circ") == 0) {
node.type = NTCircle;
} else if (strcmp(option, "disc") == 0) {
node.type = NTDisc;
} else {
throw ReadError(std::string("Unknown node option: ") + option);
}
} else {
throw ReadError(std::string("Invalid node option: ") + line);
}
line += len;
}
}
static Net::NodeID parse_node(Net& net, const char* line, Node& defaultnode, unsigned& desc)
{
Net::NodeID nodeid = Net::None;
int nodenr = -1, len;
desc = 0;
if (sscanf(line, "%d %n", &nodenr, &len) >= 1) {
nodeid = net.add_node(nodenr, defaultnode);
Node& node = net.node(nodeid);
if (line[len] != '\0') parse_node_options(node, line + len, desc);
} else {
parse_node_options(defaultnode, line, desc);
if (desc > 0)
throw ReadError("Description not allowed for default options");
}
return nodeid;
}
static void parse_arc_options(Arc& arc, const char* line, unsigned& desclen)
{
char option[11], sep[2];
int len, optpos;
for (;;) {
while (isspace(*line)) line++;
if (*line == '\0') break;
if (sscanf(line, "%10[a-z]%1[:]%n%*s%n",
option, sep, &optpos, &len) >= 2) {
if (strcmp(option, "flow") == 0) {
parse_dbl(line + optpos, arc.flow);
} else if (strcmp(option, "cost") == 0) {
parse_dbl(line + optpos, arc.cost);
} else if (strcmp(option, "color") == 0) {
parse_color(line + optpos, arc.color);
} else if (strcmp(option, "desc") == 0) {
parse_uint(line + optpos, desclen);
} else {
throw ReadError(std::string("Unknown arc option: ") + option);
}
} else {
throw ReadError(std::string("Invalid arc option: ") + option);
}
line += len;
}
}
static Net::ArcID parse_arc(Net& net, const char* line, Arc& defaultarc, unsigned& desc)
{
int srcnr, snknr;
int len;
Net::ArcID arcid = Net::None;
desc = 0;
if (sscanf(line, "%d%*[ \t]%d%n", &srcnr, &snknr, &len) >= 2) {
try {
defaultarc.src = net.nodeid(srcnr);
defaultarc.snk = net.nodeid(snknr);
arcid = net.add_arc(defaultarc);
} catch (NetError& e) {
throw ReadError(std::string("Cannot add arc: ") + e.what());
}
Arc& arc = net.arc(arcid);
parse_arc_options(arc, line + len, desc);
} else {
parse_arc_options(defaultarc, line, desc);
if (desc > 0)
throw ReadError("Description not allowed for default options");
}
return arcid;
}
static void parse_desc(std::istream& in, unsigned desclen, std::string& desc)
{
desc.resize(desclen);
in.read(&(desc[0]), desclen);
if (in.gcount() < desclen)
throw ReadError("Too few bytes for description data");
}
Net Net::from_stream(std::istream& in, Net net)
{
size_type lineno = 0;
return Net::from_stream(in, lineno, std::move(net));
}
Net Net::from_stream(std::istream& in, size_type& lineno, Net net)
{
Node defaultnode;
defaultnode.x = 0;
defaultnode.y = 0;
defaultnode.color = Color {0, 0, 0, 1.0};
defaultnode.type = NTCircle;
defaultnode.weight = 0;
Arc defaultarc;
defaultarc.src = 0;
defaultarc.snk = 0;
defaultarc.color = Color {0, 0, 0, 1.0};
defaultarc.flow = 0;
defaultarc.cost = 0;
std::string line;
char* locale = setlocale(LC_NUMERIC, "C");
try {
while (in.good()) {
std::getline(in, line);
lineno += 1;
std::string::size_type pos = line.find('#');
if (pos != std::string::npos) pos--;
pos = line.find_last_not_of(" \t\r");
if (pos == std::string::npos) continue; // empty line
line.erase(pos + 1, std::string::npos);
pos = line.find_first_not_of(" \t\r");
const char* ptr = line.c_str() + pos;
char sel[5];
int len;
if (sscanf(ptr, "%4[a-z]%*[ \t]%n", sel, &len) == 1) {
unsigned desclen;
if (strcmp(sel, "node") == 0) {
NodeID nodeid = parse_node(net, ptr + len, defaultnode, desclen);
if (desclen > 0) {
Node& n = net.node(nodeid);
parse_desc(in, desclen, n.desc);
lineno += std::count(n.desc.begin(), n.desc.end(), '\n');
}
} else if (strcmp(sel, "arc") == 0 || strcmp(sel, "edge") == 0) {
ArcID arcid = parse_arc(net, ptr + len, defaultarc, desclen);
if (arcid != Net::None && strcmp(sel, "edge") == 0) {
net.arc(arcid).directed = false;
}
if (desclen > 0) {
Arc& a = net.arc(arcid);
parse_desc(in, desclen, a.desc);
lineno += std::count(a.desc.begin(), a.desc.end(), '\n');
}
} else if (strcmp(sel, "end") == 0) {
break;
} else
throw ReadError(std::string("Unknown command: ") + sel);
} else {
throw ReadError("Expected 'arc', 'edge' or 'node'");
}
}
if (!in) throw ReadError("Unexpected IO error");
} catch (ReadError& e) {
e.set_line(lineno);
setlocale(LC_NUMERIC, locale);
throw;
} catch (...) {
setlocale(LC_NUMERIC, locale);
throw;
}
setlocale(LC_NUMERIC, locale);
return net;
}
void Net::read(std::istream& in, size_type& lineno, bool append)
{
if (!append) {
d->nodes.clear();
d->arcs.clear();
}
*this = std::move(from_stream(in, lineno, std::move(*this)));
}
void Net::read(std::istream& in, bool append)
{
size_type lineno = 0;
read(in, lineno, append);
}
}