#![allow(missing_docs)]
use std::collections::HashMap;
use pest::{Parser, iterators::Pair};
use pest_derive::Parser;
use crate::types::{Error, Result, Set};
#[allow(missing_docs)]
#[derive(Parser)]
#[grammar = "src/io/gml/grammar.pest"]
pub struct GMLParser;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct GML {
pub graph_type: String,
pub vertices: Set<String>,
pub edges: Vec<(String, String)>,
}
impl GML {
pub fn from_string(string: &str) -> Result<Self> {
let mut pairs = GMLParser::parse(Rule::file, string.trim())
.map_err(|evidence| Error::Parsing(&evidence.to_string()))?;
let pair = pairs
.next()
.ok_or_else(|| Error::Parsing("empty GML document"))?;
Self::from_pair(pair)
}
fn from_pair(pair: Pair<Rule>) -> Result<Self> {
if pair.as_rule() != Rule::graph {
return Err(Error::Parsing("expected a GML graph"));
}
let mut inner = pair.into_inner();
let list = inner
.next()
.ok_or_else(|| Error::Parsing("missing graph list"))?;
if list.as_rule() != Rule::list {
return Err(Error::Parsing("expected a GML list"));
}
let mut graph_type = "graph".to_string();
let mut vertices_map: HashMap<usize, String> = HashMap::new();
let mut edges: Vec<(usize, usize)> = Vec::new();
for item in list.into_inner() {
if item.as_rule() != Rule::item {
return Err(Error::Parsing("expected a GML item"));
}
let mut inner = item.into_inner();
let key = inner
.next()
.ok_or_else(|| Error::Parsing("missing item key"))?;
if key.as_rule() != Rule::key {
return Err(Error::Parsing("expected a GML key"));
}
let value = inner
.next()
.ok_or_else(|| Error::Parsing("missing item value"))?;
match key.as_str() {
"directed" => {
graph_type = "digraph".to_string();
}
"graphType" => {
graph_type = value.as_str().trim_matches('"').to_string();
}
"node" => {
if value.as_rule() != Rule::list {
return Err(Error::Parsing("node must be a list"));
}
let mut id: Option<usize> = None;
let mut label: Option<String> = None;
for attr in value.into_inner() {
if attr.as_rule() != Rule::item {
continue;
}
let mut ai = attr.into_inner();
let k = ai
.next()
.ok_or_else(|| Error::Parsing("missing attribute key"))?;
let v = ai
.next()
.ok_or_else(|| Error::Parsing("missing attribute value"))?;
match k.as_str() {
"id" => {
id = Some(
v.as_str()
.trim()
.parse()
.map_err(|_| Error::Parsing("invalid node id"))?,
)
}
"label" => {
label = Some(v.as_str().trim_matches('"').to_string());
}
_ => {}
}
}
let id = id.ok_or_else(|| Error::Parsing("node without id"))?;
let label = label.unwrap_or_else(|| id.to_string());
vertices_map.insert(id, label);
}
"edge" => {
if value.as_rule() != Rule::list {
return Err(Error::Parsing("edge must be a list"));
}
let mut source: Option<usize> = None;
let mut target: Option<usize> = None;
for attr in value.into_inner() {
if attr.as_rule() != Rule::item {
continue;
}
let mut ai = attr.into_inner();
let k = ai
.next()
.ok_or_else(|| Error::Parsing("missing attribute key"))?;
let v = ai
.next()
.ok_or_else(|| Error::Parsing("missing attribute value"))?;
match k.as_str() {
"source" => {
source = Some(
v.as_str()
.trim()
.parse()
.map_err(|_| Error::Parsing("invalid edge source"))?,
)
}
"target" => {
target = Some(
v.as_str()
.trim()
.parse()
.map_err(|_| Error::Parsing("invalid edge target"))?,
)
}
_ => {}
}
}
let source = source.ok_or_else(|| Error::Parsing("edge without source"))?;
let target = target.ok_or_else(|| Error::Parsing("edge without target"))?;
edges.push((source, target));
}
_ => {}
}
}
let edges: Vec<(String, String)> = edges
.into_iter()
.map(|(stats, t)| {
let stats = vertices_map
.get(&stats)
.cloned()
.ok_or_else(|| Error::Parsing("edge references unknown node"))?;
let t = vertices_map
.get(&t)
.cloned()
.ok_or_else(|| Error::Parsing("edge references unknown node"))?;
Ok((stats, t))
})
.collect::<Result<Vec<_>>>()?;
let mut vlist: Vec<String> = vertices_map.into_values().collect();
vlist.sort();
let vertices: Set<String> = Set::from_iter(vlist);
Ok(Self {
graph_type,
vertices,
edges,
})
}
}
pub(crate) fn serialize(gml: &GML) -> Result<String> {
let mut string = String::new();
string.push_str("graph [\n");
match gml.graph_type.as_ref() {
"digraph" => string.push_str("\tdirected 1\n"),
graph_type => string.push_str(&format!("\tgraphType \"{}\"\n", graph_type)),
}
for (id, label) in gml.vertices.iter().enumerate() {
string.push_str("\tnode [\n");
string.push_str(&format!("\t\tid {}\n", id));
string.push_str(&format!("\t\tlabel \"{}\"\n", label));
string.push_str("\t]\n");
}
for (source, target) in &gml.edges {
let sid = gml
.vertices
.get_index_of(source)
.ok_or_else(|| Error::Parsing("edge references unknown node"))?;
let tid = gml
.vertices
.get_index_of(target)
.ok_or_else(|| Error::Parsing("edge references unknown node"))?;
string.push_str("\tedge [\n");
string.push_str(&format!("\t\tsource {}\n", sid));
string.push_str(&format!("\t\ttarget {}\n", tid));
string.push_str("\t]\n");
}
string.push_str("]\n");
Ok(string)
}
pub trait GmlIO: Sized {
fn from_gml_string(gml: &str) -> Result<Self>;
fn to_gml_string(&self) -> Result<String>;
fn from_gml_file(path: &str) -> Result<Self>;
fn to_gml_file(&self, path: &str) -> Result<()>;
}