1use std::collections::HashMap;
14use std::io::{BufRead, BufReader, Read, Write};
15
16use crate::core::attributes::AttributeValue;
17use crate::core::{Graph, IgraphError, IgraphResult};
18
19pub fn read_gml<R: Read>(input: R) -> IgraphResult<Graph> {
54 let reader = BufReader::new(input);
55 let tokens = tokenize(reader)?;
56 parse_graph(&tokens)
57}
58
59pub fn write_gml<W: Write>(graph: &Graph, writer: &mut W) -> IgraphResult<()> {
90 writeln!(writer, "graph")?;
91 writeln!(writer, "[")?;
92 writeln!(writer, " directed {}", i32::from(graph.is_directed()))?;
93
94 write_graph_attrs(graph, writer)?;
95 write_node_blocks(graph, writer)?;
96 write_edge_blocks(graph, writer)?;
97
98 writeln!(writer, "]")?;
99 Ok(())
100}
101
102fn write_graph_attrs<W: Write>(graph: &Graph, writer: &mut W) -> IgraphResult<()> {
103 for &name in &graph.graph_attribute_names() {
104 if let Some(val) = graph.graph_attribute(name) {
105 write!(writer, " {name} ")?;
106 write_gml_value(val, writer)?;
107 writeln!(writer)?;
108 }
109 }
110 Ok(())
111}
112
113fn write_node_blocks<W: Write>(graph: &Graph, writer: &mut W) -> IgraphResult<()> {
114 let attr_names = graph.vertex_attribute_names();
115 for v in 0..graph.vcount() {
116 writeln!(writer, " node")?;
117 writeln!(writer, " [")?;
118 writeln!(writer, " id {v}")?;
119 for &name in &attr_names {
120 if let Some(val) = graph.vertex_attribute(name, v) {
121 write!(writer, " {name} ")?;
122 write_gml_value(val, writer)?;
123 writeln!(writer)?;
124 }
125 }
126 writeln!(writer, " ]")?;
127 }
128 Ok(())
129}
130
131fn write_edge_blocks<W: Write>(graph: &Graph, writer: &mut W) -> IgraphResult<()> {
132 let attr_names = graph.edge_attribute_names();
133 for eid in 0..graph.ecount() {
134 let eid_u32 = u32::try_from(eid).map_err(|_| IgraphError::Internal("edge id overflow"))?;
135 let (src, tgt) = graph.edge(eid_u32)?;
136 writeln!(writer, " edge")?;
137 writeln!(writer, " [")?;
138 writeln!(writer, " source {src}")?;
139 writeln!(writer, " target {tgt}")?;
140 for &name in &attr_names {
141 if let Some(val) = graph.edge_attribute(name, eid_u32) {
142 write!(writer, " {name} ")?;
143 write_gml_value(val, writer)?;
144 writeln!(writer)?;
145 }
146 }
147 writeln!(writer, " ]")?;
148 }
149 Ok(())
150}
151
152fn write_gml_value<W: Write>(val: &AttributeValue, writer: &mut W) -> IgraphResult<()> {
153 match val {
154 AttributeValue::Numeric(v) => {
155 write!(writer, "{v}")?;
156 }
157 AttributeValue::Boolean(b) => {
158 write!(writer, "{}", i32::from(*b))?;
159 }
160 AttributeValue::String(s) => {
161 write!(writer, "\"{}\"", gml_escape(s))?;
162 }
163 }
164 Ok(())
165}
166
167fn gml_escape(s: &str) -> String {
168 let mut out = String::with_capacity(s.len());
169 for c in s.chars() {
170 match c {
171 '"' => out.push_str("""),
172 '\\' => out.push_str("\\\\"),
173 '&' => out.push_str("&"),
174 '<' => out.push_str("<"),
175 '>' => out.push_str(">"),
176 _ => out.push(c),
177 }
178 }
179 out
180}
181
182#[derive(Debug, Clone, PartialEq)]
185enum Token {
186 Key(String),
187 Integer(i64),
188 Float(f64),
189 Str(String),
190 Open, Close, }
193
194fn tokenize<R: BufRead>(reader: R) -> IgraphResult<Vec<Token>> {
195 let mut tokens = Vec::new();
196 let mut line_no: usize = 0;
197
198 for line_result in reader.lines() {
199 line_no = line_no.wrapping_add(1);
200 let line = line_result?;
201 let trimmed = line.trim();
202
203 if trimmed.is_empty() {
204 continue;
205 }
206
207 let mut chars = trimmed.chars().peekable();
208 while let Some(&ch) = chars.peek() {
209 match ch {
210 ' ' | '\t' | '\r' => {
211 chars.next();
212 }
213 '[' => {
214 tokens.push(Token::Open);
215 chars.next();
216 }
217 ']' => {
218 tokens.push(Token::Close);
219 chars.next();
220 }
221 '"' => {
222 chars.next();
223 let s = read_quoted_string(&mut chars, line_no)?;
224 let decoded = decode_entities(&s);
225 tokens.push(Token::Str(decoded));
226 }
227 '#' => break,
228 _ => {
229 let word = read_word(&mut chars);
230 if word.is_empty() {
231 return Err(IgraphError::Parse {
232 line: line_no,
233 message: format!("unexpected character '{ch}'"),
234 });
235 }
236 if let Ok(i) = word.parse::<i64>() {
237 tokens.push(Token::Integer(i));
238 } else if let Ok(f) = parse_gml_float(&word) {
239 tokens.push(Token::Float(f));
240 } else {
241 tokens.push(Token::Key(word));
242 }
243 }
244 }
245 }
246 }
247
248 Ok(tokens)
249}
250
251fn read_quoted_string(
252 chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
253 line_no: usize,
254) -> IgraphResult<String> {
255 let mut s = String::new();
256 let mut escaped = false;
257 loop {
258 match chars.next() {
259 None => {
260 return Err(IgraphError::Parse {
261 line: line_no,
262 message: "unterminated string".into(),
263 });
264 }
265 Some('\\') if !escaped => {
266 escaped = true;
267 }
268 Some('"') if !escaped => break,
269 Some(c) => {
270 if escaped {
271 match c {
272 'n' => s.push('\n'),
273 't' => s.push('\t'),
274 '\\' => s.push('\\'),
275 '"' => s.push('"'),
276 _ => {
277 s.push('\\');
278 s.push(c);
279 }
280 }
281 escaped = false;
282 } else {
283 s.push(c);
284 }
285 }
286 }
287 }
288 Ok(s)
289}
290
291fn read_word(chars: &mut std::iter::Peekable<std::str::Chars<'_>>) -> String {
292 let mut word = String::new();
293 while let Some(&c) = chars.peek() {
294 if c == ' ' || c == '\t' || c == '[' || c == ']' || c == '"' {
295 break;
296 }
297 word.push(c);
298 chars.next();
299 }
300 word
301}
302
303fn parse_gml_float(s: &str) -> Result<f64, ()> {
304 let lower = s.to_ascii_lowercase();
305 match lower.as_str() {
306 "inf" | "+inf" => Ok(f64::INFINITY),
307 "-inf" => Ok(f64::NEG_INFINITY),
308 "nan" => Ok(f64::NAN),
309 _ => s.parse::<f64>().map_err(|_| ()),
310 }
311}
312
313fn decode_entities(s: &str) -> String {
314 s.replace("&", "&")
315 .replace(""", "\"")
316 .replace("'", "'")
317 .replace("<", "<")
318 .replace(">", ">")
319}
320
321struct NodeAttrs {
324 id: i64,
325 attrs: Vec<(String, AttributeValue)>,
326}
327
328struct EdgeAttrs {
329 source: i64,
330 target: i64,
331 attrs: Vec<(String, AttributeValue)>,
332}
333
334fn parse_graph(tokens: &[Token]) -> IgraphResult<Graph> {
335 let mut pos = 0;
336
337 let mut graph_attrs: Vec<(String, AttributeValue)> = Vec::new();
339
340 while pos < tokens.len() {
341 match &tokens[pos] {
342 Token::Key(k) if k == "graph" => {
343 pos += 1;
344 break;
345 }
346 Token::Key(k) => {
347 let key = k.clone();
348 pos += 1;
349 if let Some((val, new_pos)) = try_read_simple_value(tokens, pos) {
350 graph_attrs.push((key, val));
351 pos = new_pos;
352 } else {
353 pos = skip_value(tokens, pos);
354 }
355 }
356 _ => {
357 pos += 1;
358 }
359 }
360 }
361
362 if pos >= tokens.len() {
363 return Err(IgraphError::Parse {
364 line: 0,
365 message: "no 'graph' block found in GML input".into(),
366 });
367 }
368 if tokens[pos] != Token::Open {
369 return Err(IgraphError::Parse {
370 line: 0,
371 message: "expected '[' after 'graph'".into(),
372 });
373 }
374 pos += 1;
375
376 let mut directed = false;
377 let mut nodes: Vec<NodeAttrs> = Vec::new();
378 let mut edges: Vec<EdgeAttrs> = Vec::new();
379
380 while pos < tokens.len() && tokens[pos] != Token::Close {
381 match &tokens[pos] {
382 Token::Key(k) => {
383 let key = k.clone();
384 pos += 1;
385
386 match key.as_str() {
387 "directed" => {
388 if let Some(Token::Integer(v)) = tokens.get(pos) {
389 directed = *v != 0;
390 pos += 1;
391 } else {
392 pos = skip_value(tokens, pos);
393 }
394 }
395 "node" if pos < tokens.len() && tokens[pos] == Token::Open => {
396 pos += 1;
397 let (node, new_pos) = parse_node(tokens, pos)?;
398 nodes.push(node);
399 pos = new_pos;
400 }
401 "edge" if pos < tokens.len() && tokens[pos] == Token::Open => {
402 pos += 1;
403 let (edge, new_pos) = parse_edge(tokens, pos)?;
404 edges.push(edge);
405 pos = new_pos;
406 }
407 _ => {
408 if let Some((val, new_pos)) = try_read_simple_value(tokens, pos) {
409 graph_attrs.push((key, val));
410 pos = new_pos;
411 } else {
412 pos = skip_value(tokens, pos);
413 }
414 }
415 }
416 }
417 _ => {
418 return Err(IgraphError::Parse {
419 line: 0,
420 message: format!("unexpected token in graph block: {:?}", tokens[pos]),
421 });
422 }
423 }
424 }
425
426 build_graph(directed, &nodes, &edges, &graph_attrs)
427}
428
429fn try_read_simple_value(tokens: &[Token], pos: usize) -> Option<(AttributeValue, usize)> {
430 match tokens.get(pos)? {
431 #[allow(clippy::cast_precision_loss)]
432 Token::Integer(i) => Some((AttributeValue::Numeric(*i as f64), pos + 1)),
433 Token::Float(f) => Some((AttributeValue::Numeric(*f), pos + 1)),
434 Token::Str(s) => Some((AttributeValue::String(s.clone()), pos + 1)),
435 _ => None,
436 }
437}
438
439fn parse_node(tokens: &[Token], mut pos: usize) -> IgraphResult<(NodeAttrs, usize)> {
440 let mut node_id: Option<i64> = None;
441 let mut attrs: Vec<(String, AttributeValue)> = Vec::new();
442
443 while pos < tokens.len() && tokens[pos] != Token::Close {
444 match &tokens[pos] {
445 Token::Key(k) => {
446 let key = k.clone();
447 pos += 1;
448 if key == "id" {
449 if let Some(Token::Integer(v)) = tokens.get(pos) {
450 node_id = Some(*v);
451 pos += 1;
452 } else {
453 pos = skip_value(tokens, pos);
454 }
455 } else if let Some((val, new_pos)) = try_read_simple_value(tokens, pos) {
456 attrs.push((key, val));
457 pos = new_pos;
458 } else {
459 pos = skip_value(tokens, pos);
460 }
461 }
462 _ => {
463 return Err(IgraphError::Parse {
464 line: 0,
465 message: format!("unexpected token in node block: {:?}", tokens[pos]),
466 });
467 }
468 }
469 }
470
471 if pos < tokens.len() && tokens[pos] == Token::Close {
472 pos += 1;
473 }
474
475 let id = node_id.ok_or_else(|| IgraphError::Parse {
476 line: 0,
477 message: "node without 'id' field".into(),
478 })?;
479
480 Ok((NodeAttrs { id, attrs }, pos))
481}
482
483fn parse_edge(tokens: &[Token], mut pos: usize) -> IgraphResult<(EdgeAttrs, usize)> {
484 let mut source: Option<i64> = None;
485 let mut target: Option<i64> = None;
486 let mut attrs: Vec<(String, AttributeValue)> = Vec::new();
487
488 while pos < tokens.len() && tokens[pos] != Token::Close {
489 match &tokens[pos] {
490 Token::Key(k) => {
491 let key = k.clone();
492 pos += 1;
493 match key.as_str() {
494 "source" => {
495 if let Some(Token::Integer(v)) = tokens.get(pos) {
496 source = Some(*v);
497 pos += 1;
498 } else {
499 pos = skip_value(tokens, pos);
500 }
501 }
502 "target" => {
503 if let Some(Token::Integer(v)) = tokens.get(pos) {
504 target = Some(*v);
505 pos += 1;
506 } else {
507 pos = skip_value(tokens, pos);
508 }
509 }
510 _ => {
511 if let Some((val, new_pos)) = try_read_simple_value(tokens, pos) {
512 attrs.push((key, val));
513 pos = new_pos;
514 } else {
515 pos = skip_value(tokens, pos);
516 }
517 }
518 }
519 }
520 _ => {
521 return Err(IgraphError::Parse {
522 line: 0,
523 message: format!("unexpected token in edge block: {:?}", tokens[pos]),
524 });
525 }
526 }
527 }
528
529 if pos < tokens.len() && tokens[pos] == Token::Close {
530 pos += 1;
531 }
532
533 let src = source.ok_or_else(|| IgraphError::Parse {
534 line: 0,
535 message: "edge without 'source' field".into(),
536 })?;
537 let tgt = target.ok_or_else(|| IgraphError::Parse {
538 line: 0,
539 message: "edge without 'target' field".into(),
540 })?;
541
542 Ok((
543 EdgeAttrs {
544 source: src,
545 target: tgt,
546 attrs,
547 },
548 pos,
549 ))
550}
551
552fn skip_value(tokens: &[Token], mut pos: usize) -> usize {
553 if pos >= tokens.len() {
554 return pos;
555 }
556 match &tokens[pos] {
557 Token::Integer(_) | Token::Float(_) | Token::Str(_) | Token::Key(_) => pos + 1,
558 Token::Open => {
559 pos += 1;
560 let mut depth: u32 = 1;
561 while pos < tokens.len() && depth > 0 {
562 match &tokens[pos] {
563 Token::Open => depth = depth.saturating_add(1),
564 Token::Close => depth = depth.saturating_sub(1),
565 _ => {}
566 }
567 pos += 1;
568 }
569 pos
570 }
571 Token::Close => pos,
572 }
573}
574
575fn build_graph(
576 directed: bool,
577 nodes: &[NodeAttrs],
578 edges: &[EdgeAttrs],
579 graph_attrs: &[(String, AttributeValue)],
580) -> IgraphResult<Graph> {
581 let mut id_map: HashMap<i64, u32> = HashMap::with_capacity(nodes.len());
582 for (idx, node) in nodes.iter().enumerate() {
583 let internal_id =
584 u32::try_from(idx).map_err(|_| IgraphError::Internal("node index overflow"))?;
585 if id_map.insert(node.id, internal_id).is_some() {
586 return Err(IgraphError::Parse {
587 line: 0,
588 message: format!("duplicate node id {}", node.id),
589 });
590 }
591 }
592
593 let n = u32::try_from(nodes.len()).map_err(|_| IgraphError::Internal("node count overflow"))?;
594 let mut graph = Graph::new(n, directed)?;
595
596 for (key, val) in graph_attrs {
598 graph.set_graph_attribute(key, val.clone());
599 }
600
601 for (idx, node) in nodes.iter().enumerate() {
603 let vid = u32::try_from(idx).map_err(|_| IgraphError::Internal("vertex id overflow"))?;
604 for (key, val) in &node.attrs {
605 graph.set_vertex_attribute(key, vid, val.clone())?;
606 }
607 }
608
609 for (eid_idx, edge) in edges.iter().enumerate() {
611 let src = id_map.get(&edge.source).ok_or_else(|| IgraphError::Parse {
612 line: 0,
613 message: format!("edge references unknown node id {}", edge.source),
614 })?;
615 let tgt = id_map.get(&edge.target).ok_or_else(|| IgraphError::Parse {
616 line: 0,
617 message: format!("edge references unknown node id {}", edge.target),
618 })?;
619 graph.add_edge(*src, *tgt)?;
620
621 let eid = u32::try_from(eid_idx).map_err(|_| IgraphError::Internal("edge id overflow"))?;
622 for (key, val) in &edge.attrs {
623 graph.set_edge_attribute(key, eid, val.clone())?;
624 }
625 }
626
627 Ok(graph)
628}
629
630#[cfg(test)]
631mod tests {
632 use super::*;
633
634 #[test]
635 fn test_empty_graph() {
636 let gml = b"graph [ ]";
637 let g = read_gml(&gml[..]).unwrap();
638 assert_eq!(g.vcount(), 0);
639 assert_eq!(g.ecount(), 0);
640 assert!(!g.is_directed());
641 }
642
643 #[test]
644 fn test_directed_flag() {
645 let gml = b"graph [ directed 1 node [ id 0 ] node [ id 1 ] edge [ source 0 target 1 ] ]";
646 let g = read_gml(&gml[..]).unwrap();
647 assert!(g.is_directed());
648 assert_eq!(g.vcount(), 2);
649 assert_eq!(g.ecount(), 1);
650 }
651
652 #[test]
653 fn test_undirected_default() {
654 let gml = b"graph [ node [ id 0 ] node [ id 1 ] edge [ source 0 target 1 ] ]";
655 let g = read_gml(&gml[..]).unwrap();
656 assert!(!g.is_directed());
657 }
658
659 #[test]
660 fn test_non_contiguous_ids() {
661 let gml = b"graph [\n node [ id 10 ]\n node [ id 20 ]\n node [ id 30 ]\n edge [ source 10 target 30 ]\n]";
662 let g = read_gml(&gml[..]).unwrap();
663 assert_eq!(g.vcount(), 3);
664 assert_eq!(g.ecount(), 1);
665 let (src, tgt) = g.edge(0).unwrap();
666 assert_eq!(src, 0);
667 assert_eq!(tgt, 2);
668 }
669
670 #[test]
671 fn test_multiline_with_attributes() {
672 let gml = b"Creator \"test\"\ngraph\n[\n directed 0\n node\n [\n id 0\n label \"A\"\n ]\n node\n [\n id 1\n label \"B\"\n ]\n edge\n [\n source 0\n target 1\n weight 1.5\n ]\n]\n";
673 let g = read_gml(&gml[..]).unwrap();
674 assert_eq!(g.vcount(), 2);
675 assert_eq!(g.ecount(), 1);
676 assert_eq!(
677 g.vertex_attribute("label", 0).and_then(|v| v.as_str()),
678 Some("A"),
679 );
680 assert_eq!(
681 g.vertex_attribute("label", 1).and_then(|v| v.as_str()),
682 Some("B"),
683 );
684 let w = g
685 .edge_attribute("weight", 0)
686 .and_then(AttributeValue::as_f64);
687 assert!((w.unwrap() - 1.5).abs() < f64::EPSILON);
688 assert_eq!(
689 g.graph_attribute("Creator").and_then(|v| v.as_str()),
690 Some("test"),
691 );
692 }
693
694 #[test]
695 fn test_self_loop() {
696 let gml = b"graph [ node [ id 0 ] edge [ source 0 target 0 ] ]";
697 let g = read_gml(&gml[..]).unwrap();
698 assert_eq!(g.ecount(), 1);
699 let (s, t) = g.edge(0).unwrap();
700 assert_eq!(s, t);
701 }
702
703 #[test]
704 fn test_multiple_edges() {
705 let gml = b"graph [ node [ id 0 ] node [ id 1 ] edge [ source 0 target 1 ] edge [ source 0 target 1 ] ]";
706 let g = read_gml(&gml[..]).unwrap();
707 assert_eq!(g.ecount(), 2);
708 }
709
710 #[test]
711 fn test_error_missing_graph() {
712 let gml = b"node [ id 0 ]";
713 let result = read_gml(&gml[..]);
714 assert!(result.is_err());
715 }
716
717 #[test]
718 fn test_error_duplicate_node_id() {
719 let gml = b"graph [ node [ id 0 ] node [ id 0 ] ]";
720 let result = read_gml(&gml[..]);
721 assert!(result.is_err());
722 }
723
724 #[test]
725 fn test_error_unknown_edge_target() {
726 let gml = b"graph [ node [ id 0 ] edge [ source 0 target 99 ] ]";
727 let result = read_gml(&gml[..]);
728 assert!(result.is_err());
729 }
730
731 #[test]
732 fn test_error_node_without_id() {
733 let gml = b"graph [ node [ label \"x\" ] ]";
734 let result = read_gml(&gml[..]);
735 assert!(result.is_err());
736 }
737
738 #[test]
739 fn test_error_edge_without_source() {
740 let gml = b"graph [ node [ id 0 ] node [ id 1 ] edge [ target 1 ] ]";
741 let result = read_gml(&gml[..]);
742 assert!(result.is_err());
743 }
744
745 #[test]
746 fn test_write_read_round_trip_undirected() {
747 let mut g = Graph::with_vertices(4);
748 g.add_edge(0, 1).unwrap();
749 g.add_edge(1, 2).unwrap();
750 g.add_edge(2, 3).unwrap();
751 g.add_edge(3, 0).unwrap();
752
753 let mut buf = Vec::new();
754 write_gml(&g, &mut buf).unwrap();
755
756 let g2 = read_gml(&buf[..]).unwrap();
757 assert_eq!(g2.vcount(), 4);
758 assert_eq!(g2.ecount(), 4);
759 assert!(!g2.is_directed());
760 }
761
762 #[test]
763 fn test_write_read_round_trip_directed() {
764 let mut g = Graph::new(3, true).unwrap();
765 g.add_edge(0, 1).unwrap();
766 g.add_edge(1, 2).unwrap();
767 g.add_edge(2, 0).unwrap();
768
769 let mut buf = Vec::new();
770 write_gml(&g, &mut buf).unwrap();
771
772 let g2 = read_gml(&buf[..]).unwrap();
773 assert_eq!(g2.vcount(), 3);
774 assert_eq!(g2.ecount(), 3);
775 assert!(g2.is_directed());
776
777 for eid in 0..3u32 {
778 let (s1, t1) = g.edge(eid).unwrap();
779 let (s2, t2) = g2.edge(eid).unwrap();
780 assert_eq!(s1, s2);
781 assert_eq!(t1, t2);
782 }
783 }
784
785 #[test]
786 fn test_write_empty() {
787 let g = Graph::with_vertices(0);
788 let mut buf = Vec::new();
789 write_gml(&g, &mut buf).unwrap();
790 let s = String::from_utf8(buf).unwrap();
791 assert!(s.contains("graph"));
792 assert!(s.contains("directed 0"));
793 }
794
795 #[test]
796 fn test_string_with_entities() {
797 let gml = b"graph [ node [ id 0 label \"a&b\" ] ]";
798 let g = read_gml(&gml[..]).unwrap();
799 assert_eq!(g.vcount(), 1);
800 assert_eq!(
801 g.vertex_attribute("label", 0).and_then(|v| v.as_str()),
802 Some("a&b"),
803 );
804 }
805
806 #[test]
807 fn test_top_level_creator_as_graph_attr() {
808 let gml = b"Creator \"Someone\"\nVersion 1\ngraph [ node [ id 0 ] ]";
809 let g = read_gml(&gml[..]).unwrap();
810 assert_eq!(g.vcount(), 1);
811 assert_eq!(
812 g.graph_attribute("Creator").and_then(|v| v.as_str()),
813 Some("Someone"),
814 );
815 }
816
817 #[test]
818 fn test_negative_node_id() {
819 let gml = b"graph [ node [ id -5 ] node [ id -3 ] edge [ source -5 target -3 ] ]";
820 let g = read_gml(&gml[..]).unwrap();
821 assert_eq!(g.vcount(), 2);
822 assert_eq!(g.ecount(), 1);
823 }
824
825 #[test]
826 fn test_lesmis_style_header() {
827 let gml = b"Creator \"Mark Newman on Fri Jul 21 12:44:53 2006\"\ngraph\n[\n node\n [\n id 0\n label \"Myriel\"\n ]\n node\n [\n id 1\n label \"Napoleon\"\n ]\n edge\n [\n source 0\n target 1\n value 1\n ]\n]\n";
828 let g = read_gml(&gml[..]).unwrap();
829 assert_eq!(g.vcount(), 2);
830 assert_eq!(g.ecount(), 1);
831 assert_eq!(
832 g.vertex_attribute("label", 0).and_then(|v| v.as_str()),
833 Some("Myriel"),
834 );
835 assert_eq!(
836 g.vertex_attribute("label", 1).and_then(|v| v.as_str()),
837 Some("Napoleon"),
838 );
839 let v = g
840 .edge_attribute("value", 0)
841 .and_then(AttributeValue::as_f64);
842 assert!((v.unwrap() - 1.0).abs() < f64::EPSILON);
843 }
844
845 #[test]
846 fn roundtrip_with_attributes() {
847 let mut g = Graph::from_edges(&[(0, 1), (1, 2)], false, None).unwrap();
848 g.set_vertex_attribute("label", 0, "Alice".into()).unwrap();
849 g.set_vertex_attribute("label", 1, "Bob".into()).unwrap();
850 g.set_vertex_attribute("label", 2, "Carol".into()).unwrap();
851 g.set_edge_attribute("weight", 0, 1.5.into()).unwrap();
852 g.set_edge_attribute("weight", 1, 2.5.into()).unwrap();
853 g.set_graph_attribute("name", "test_graph".into());
854
855 let mut buf = Vec::new();
856 write_gml(&g, &mut buf).unwrap();
857
858 let g2 = read_gml(&buf[..]).unwrap();
859 assert_eq!(g2.vcount(), 3);
860 assert_eq!(g2.ecount(), 2);
861 assert_eq!(
862 g2.vertex_attribute("label", 0).and_then(|v| v.as_str()),
863 Some("Alice"),
864 );
865 assert_eq!(
866 g2.vertex_attribute("label", 2).and_then(|v| v.as_str()),
867 Some("Carol"),
868 );
869 let w = g2
870 .edge_attribute("weight", 0)
871 .and_then(AttributeValue::as_f64);
872 assert!((w.unwrap() - 1.5).abs() < f64::EPSILON);
873 assert_eq!(
874 g2.graph_attribute("name").and_then(|v| v.as_str()),
875 Some("test_graph"),
876 );
877 }
878
879 #[test]
880 fn write_boolean_attribute() {
881 let mut g = Graph::with_vertices(2);
882 g.add_edge(0, 1).unwrap();
883 g.set_vertex_attribute("active", 0, true.into()).unwrap();
884 g.set_vertex_attribute("active", 1, false.into()).unwrap();
885
886 let mut buf = Vec::new();
887 write_gml(&g, &mut buf).unwrap();
888 let s = String::from_utf8(buf).unwrap();
889 assert!(s.contains("active 1"));
890 assert!(s.contains("active 0"));
891 }
892}