1use std::collections::HashMap;
11use std::io::{BufRead, BufReader, Read, Write};
12
13use crate::core::attributes::AttributeValue;
14use crate::core::{Graph, IgraphError, IgraphResult};
15
16pub struct GraphmlGraph {
18 pub graph: Graph,
20 pub labels: Vec<String>,
23}
24
25#[derive(Debug, Clone)]
26struct KeyDef {
27 attr_name: String,
28 attr_type: String,
29}
30
31fn parse_attr_value(raw: &str, attr_type: &str) -> AttributeValue {
32 match attr_type {
33 "int" | "long" | "float" | "double" => {
34 if let Ok(v) = raw.parse::<f64>() {
35 AttributeValue::Numeric(v)
36 } else {
37 AttributeValue::String(raw.to_owned())
38 }
39 }
40 "boolean" => {
41 let v = matches!(raw, "true" | "1" | "yes");
42 AttributeValue::Boolean(v)
43 }
44 _ => AttributeValue::String(raw.to_owned()),
45 }
46}
47
48#[allow(clippy::too_many_lines)]
84pub fn read_graphml<R: Read>(input: R) -> IgraphResult<GraphmlGraph> {
85 let reader = BufReader::new(input);
86 let mut content = String::new();
87 for line in reader.lines() {
88 content.push_str(&line?);
89 content.push('\n');
90 }
91
92 let keys = parse_key_definitions(&content)?;
93 let graph_section = extract_graph_section(&content)?;
94 let directed = parse_edgedefault(graph_section);
95
96 let parsed = parse_nodes(graph_section)?;
97
98 let n = u32::try_from(parsed.ids.len())
99 .map_err(|_| IgraphError::InvalidArgument("too many nodes for u32".to_owned()))?;
100 let mut graph = Graph::new(n, directed)?;
101
102 if !parsed.ids.is_empty() {
103 let label_vals: Vec<AttributeValue> = parsed
104 .ids
105 .iter()
106 .map(|s| AttributeValue::String(s.clone()))
107 .collect();
108 graph.set_vertex_attribute_all("name", label_vals)?;
109 }
110
111 apply_node_attributes(&mut graph, &parsed.data, &keys)?;
112
113 let edge_data_list = parse_and_add_edges(&mut graph, graph_section, &parsed.map)?;
114
115 apply_edge_attributes(&mut graph, &edge_data_list, &keys)?;
116 apply_graph_attributes(&mut graph, graph_section, &keys);
117
118 Ok(GraphmlGraph {
119 graph,
120 labels: parsed.ids,
121 })
122}
123
124fn parse_key_definitions(content: &str) -> IgraphResult<HashMap<String, KeyDef>> {
125 let mut keys: HashMap<String, KeyDef> = HashMap::new();
126 let mut pos = 0;
127 while let Some(key_start) = content[pos..].find("<key") {
128 let abs_start = pos + key_start;
129 let after = abs_start + 4;
130 match content.as_bytes().get(after) {
131 Some(b' ' | b'>' | b'/' | b'\t' | b'\n' | b'\r') | None => {}
132 _ => {
133 pos = abs_start + 1;
134 continue;
135 }
136 }
137 let tag_end = find_tag_end(&content[abs_start..]).ok_or_else(|| IgraphError::Parse {
138 line: 0,
139 message: "unclosed <key> tag".to_owned(),
140 })?;
141 let tag = &content[abs_start..abs_start + tag_end];
142 if let Some(id) = extract_attr(tag, "id") {
143 let attr_name = extract_attr(tag, "attr.name").unwrap_or_else(|| id.clone());
144 let attr_type = extract_attr(tag, "attr.type").unwrap_or_else(|| "string".to_owned());
145 keys.insert(
146 id,
147 KeyDef {
148 attr_name,
149 attr_type,
150 },
151 );
152 }
153 pos = abs_start + tag_end;
154 }
155 Ok(keys)
156}
157
158fn extract_graph_section(content: &str) -> IgraphResult<&str> {
159 let graph_start = find_graph_element(content).ok_or_else(|| IgraphError::Parse {
160 line: 0,
161 message: "no <graph> element found".to_owned(),
162 })?;
163 let graph_end = content[graph_start..]
164 .find("</graph>")
165 .or_else(|| content[graph_start..].find("/>"))
166 .ok_or_else(|| IgraphError::Parse {
167 line: 0,
168 message: "unclosed <graph> element".to_owned(),
169 })?;
170 Ok(&content[graph_start..graph_start + graph_end + 8])
171}
172
173struct ParsedNodes {
174 ids: Vec<String>,
175 map: HashMap<String, u32>,
176 data: Vec<Vec<(String, String)>>,
177}
178
179fn parse_nodes(graph_section: &str) -> IgraphResult<ParsedNodes> {
180 let mut node_ids: Vec<String> = Vec::new();
181 let mut node_map: HashMap<String, u32> = HashMap::new();
182 let mut node_data: Vec<Vec<(String, String)>> = Vec::new();
183
184 let mut pos = 0;
185 while let Some(node_start) = graph_section[pos..].find("<node") {
186 let abs_start = pos + node_start;
187 let after = abs_start + 5;
188 match graph_section.as_bytes().get(after) {
189 Some(b' ' | b'>' | b'/' | b'\t' | b'\n' | b'\r') | None => {}
190 _ => {
191 pos = abs_start + 1;
192 continue;
193 }
194 }
195
196 let tag_end_pos =
197 find_tag_end(&graph_section[abs_start..]).ok_or_else(|| IgraphError::Parse {
198 line: 0,
199 message: "unclosed <node> tag".to_owned(),
200 })?;
201 let tag = &graph_section[abs_start..abs_start + tag_end_pos];
202
203 if let Some(id) = extract_attr(tag, "id") {
204 let idx = u32::try_from(node_ids.len())
205 .map_err(|_| IgraphError::InvalidArgument("too many nodes for u32".to_owned()))?;
206 node_map.insert(id.clone(), idx);
207 node_ids.push(id);
208
209 let node_block_end;
210 if tag.ends_with("/>") {
211 node_block_end = abs_start + tag_end_pos;
212 node_data.push(Vec::new());
213 } else if let Some(close) = graph_section[abs_start..].find("</node>") {
214 node_block_end = abs_start + close + 7;
215 let block = &graph_section[abs_start + tag_end_pos..abs_start + close];
216 node_data.push(extract_data_elements(block));
217 } else {
218 node_block_end = abs_start + tag_end_pos;
219 node_data.push(Vec::new());
220 }
221 pos = node_block_end;
222 } else {
223 pos = abs_start + tag_end_pos;
224 }
225 }
226 Ok(ParsedNodes {
227 ids: node_ids,
228 map: node_map,
229 data: node_data,
230 })
231}
232
233fn apply_node_attributes(
234 graph: &mut Graph,
235 node_data: &[Vec<(String, String)>],
236 keys: &HashMap<String, KeyDef>,
237) -> IgraphResult<()> {
238 for (vid, data_items) in node_data.iter().enumerate() {
239 for (key_id, raw_val) in data_items {
240 if let Some(key_def) = keys.get(key_id) {
241 let value = parse_attr_value(raw_val, &key_def.attr_type);
242 let vid_u32 =
243 u32::try_from(vid).map_err(|_| IgraphError::Internal("vertex id overflow"))?;
244 graph.set_vertex_attribute(&key_def.attr_name, vid_u32, value)?;
245 }
246 }
247 }
248 Ok(())
249}
250
251fn parse_and_add_edges(
252 graph: &mut Graph,
253 graph_section: &str,
254 node_map: &HashMap<String, u32>,
255) -> IgraphResult<Vec<Vec<(String, String)>>> {
256 let mut edge_data_list: Vec<Vec<(String, String)>> = Vec::new();
257 let mut pos = 0;
258 while let Some(edge_start) = graph_section[pos..].find("<edge") {
259 let abs_start = pos + edge_start;
260 let tag_end_pos =
261 find_tag_end(&graph_section[abs_start..]).ok_or_else(|| IgraphError::Parse {
262 line: 0,
263 message: "unclosed <edge> tag".to_owned(),
264 })?;
265 let tag = &graph_section[abs_start..abs_start + tag_end_pos];
266 let source = extract_attr(tag, "source").ok_or_else(|| IgraphError::Parse {
267 line: 0,
268 message: "edge missing source attribute".to_owned(),
269 })?;
270 let target = extract_attr(tag, "target").ok_or_else(|| IgraphError::Parse {
271 line: 0,
272 message: "edge missing target attribute".to_owned(),
273 })?;
274
275 let src_idx = *node_map.get(&source).ok_or_else(|| IgraphError::Parse {
276 line: 0,
277 message: format!("edge references unknown node \"{source}\""),
278 })?;
279 let tgt_idx = *node_map.get(&target).ok_or_else(|| IgraphError::Parse {
280 line: 0,
281 message: format!("edge references unknown node \"{target}\""),
282 })?;
283 graph.add_edge(src_idx, tgt_idx)?;
284
285 if tag.ends_with("/>") {
286 edge_data_list.push(Vec::new());
287 } else if let Some(close) = graph_section[abs_start..].find("</edge>") {
288 let block = &graph_section[abs_start + tag_end_pos..abs_start + close];
289 edge_data_list.push(extract_data_elements(block));
290 pos = abs_start + close + 7;
291 continue;
292 } else {
293 edge_data_list.push(Vec::new());
294 }
295 pos = abs_start + tag_end_pos;
296 }
297 Ok(edge_data_list)
298}
299
300fn apply_edge_attributes(
301 graph: &mut Graph,
302 edge_data_list: &[Vec<(String, String)>],
303 keys: &HashMap<String, KeyDef>,
304) -> IgraphResult<()> {
305 for (eid, data_items) in edge_data_list.iter().enumerate() {
306 for (key_id, raw_val) in data_items {
307 if let Some(key_def) = keys.get(key_id) {
308 let value = parse_attr_value(raw_val, &key_def.attr_type);
309 let eid_u32 =
310 u32::try_from(eid).map_err(|_| IgraphError::Internal("edge id overflow"))?;
311 graph.set_edge_attribute(&key_def.attr_name, eid_u32, value)?;
312 }
313 }
314 }
315 Ok(())
316}
317
318fn apply_graph_attributes(graph: &mut Graph, graph_section: &str, keys: &HashMap<String, KeyDef>) {
319 let graph_tag_end = find_tag_end(graph_section).unwrap_or(graph_section.len());
320 let graph_inner = &graph_section[graph_tag_end..];
321 let first_child = graph_inner
322 .find("<node")
323 .unwrap_or(graph_inner.len())
324 .min(graph_inner.find("<edge").unwrap_or(graph_inner.len()));
325 let preamble = &graph_inner[..first_child];
326 for (key_id, raw_val) in extract_data_elements(preamble) {
327 if let Some(key_def) = keys.get(&key_id) {
328 let value = parse_attr_value(&raw_val, &key_def.attr_type);
329 graph.set_graph_attribute(&key_def.attr_name, value);
330 }
331 }
332}
333
334fn extract_data_elements(block: &str) -> Vec<(String, String)> {
335 let mut result = Vec::new();
336 let mut pos = 0;
337 while let Some(data_start) = block[pos..].find("<data") {
338 let abs_start = pos + data_start;
339 let Some(tag_end) = find_tag_end(&block[abs_start..]) else {
340 break;
341 };
342 let tag = &block[abs_start..abs_start + tag_end];
343
344 if let Some(key_id) = extract_attr(tag, "key") {
345 if tag.ends_with("/>") {
346 result.push((key_id, String::new()));
347 pos = abs_start + tag_end;
348 } else if let Some(close) = block[abs_start..].find("</data>") {
349 let content = &block[abs_start + tag_end..abs_start + close];
350 let unescaped = xml_unescape(content.trim());
351 result.push((key_id, unescaped));
352 pos = abs_start + close + 7;
353 } else {
354 pos = abs_start + tag_end;
355 }
356 } else {
357 pos = abs_start + tag_end;
358 }
359 }
360 result
361}
362
363fn find_graph_element(s: &str) -> Option<usize> {
365 let mut search_from = 0;
366 while let Some(pos) = s[search_from..].find("<graph") {
367 let abs = search_from + pos;
368 let after = abs + 6;
369 match s.as_bytes().get(after) {
370 Some(b' ' | b'>' | b'/' | b'\t' | b'\n' | b'\r') | None => return Some(abs),
371 _ => search_from = abs + 1,
372 }
373 }
374 None
375}
376
377fn parse_edgedefault(graph_tag: &str) -> bool {
378 if let Some(pos) = graph_tag.find("edgedefault") {
379 let rest = &graph_tag[pos..];
380 if let Some(q1) = rest.find('"') {
381 let after_q = &rest[q1 + 1..];
382 if let Some(q2) = after_q.find('"') {
383 let val = &after_q[..q2];
384 return val == "directed";
385 }
386 }
387 if let Some(q1) = rest.find('\'') {
388 let after_q = &rest[q1 + 1..];
389 if let Some(q2) = after_q.find('\'') {
390 let val = &after_q[..q2];
391 return val == "directed";
392 }
393 }
394 }
395 false
396}
397
398fn find_tag_end(s: &str) -> Option<usize> {
399 s.find('>').map(|p| p + 1)
400}
401
402fn extract_attr(tag: &str, attr_name: &str) -> Option<String> {
403 let needle = format!("{attr_name}=");
404 let pos = tag.find(&needle)?;
405 let rest = &tag[pos + needle.len()..];
406 let quote = rest.chars().next()?;
407 if quote != '"' && quote != '\'' {
408 return None;
409 }
410 let after = &rest[1..];
411 let end = after.find(quote)?;
412 let raw = &after[..end];
413 Some(xml_unescape(raw))
414}
415
416fn xml_unescape(s: &str) -> String {
417 s.replace("&", "&")
418 .replace("<", "<")
419 .replace(">", ">")
420 .replace(""", "\"")
421 .replace("'", "'")
422}
423
424#[allow(clippy::too_many_lines)]
450pub fn write_graphml<W: Write>(
451 graph: &Graph,
452 labels: Option<&[String]>,
453 writer: &mut W,
454) -> IgraphResult<()> {
455 if let Some(l) = labels {
456 if l.len() != graph.vcount() as usize {
457 return Err(IgraphError::InvalidArgument(format!(
458 "labels length {} does not match vcount {}",
459 l.len(),
460 graph.vcount()
461 )));
462 }
463 }
464
465 let edge_default = if graph.is_directed() {
466 "directed"
467 } else {
468 "undirected"
469 };
470
471 writeln!(writer, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")?;
472 writeln!(
473 writer,
474 "<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\">"
475 )?;
476
477 let graph_attr_names = graph.graph_attribute_names();
478 let vertex_attr_names = graph.vertex_attribute_names();
479 let edge_attr_names = graph.edge_attribute_names();
480
481 let mut key_counter = 0u32;
482 let graph_key_map = write_graph_keys(graph, &graph_attr_names, &mut key_counter, writer)?;
483 let vertex_key_map = write_vertex_keys(graph, &vertex_attr_names, &mut key_counter, writer)?;
484 let edge_key_map = write_edge_keys(graph, &edge_attr_names, &mut key_counter, writer)?;
485
486 writeln!(writer, " <graph id=\"G\" edgedefault=\"{edge_default}\">")?;
487
488 write_graph_data(graph, &graph_attr_names, &graph_key_map, writer)?;
489 write_nodes(graph, &vertex_attr_names, &vertex_key_map, labels, writer)?;
490 write_edges(graph, &edge_attr_names, &edge_key_map, labels, writer)?;
491
492 writeln!(writer, " </graph>")?;
493 writeln!(writer, "</graphml>")?;
494
495 Ok(())
496}
497
498fn write_graph_keys<W: Write>(
499 graph: &Graph,
500 names: &[&str],
501 counter: &mut u32,
502 writer: &mut W,
503) -> IgraphResult<HashMap<String, String>> {
504 let mut map = HashMap::new();
505 for &name in names {
506 let key_id = format!("g{counter}");
507 *counter = counter.wrapping_add(1);
508 let attr_type = graph.graph_attribute(name).map_or("string", attr_type_str);
509 writeln!(
510 writer,
511 " <key id=\"{key_id}\" for=\"graph\" attr.name=\"{}\" attr.type=\"{attr_type}\"/>",
512 xml_escape(name)
513 )?;
514 map.insert(name.to_owned(), key_id);
515 }
516 Ok(map)
517}
518
519fn write_vertex_keys<W: Write>(
520 graph: &Graph,
521 names: &[&str],
522 counter: &mut u32,
523 writer: &mut W,
524) -> IgraphResult<HashMap<String, String>> {
525 let mut map = HashMap::new();
526 for &name in names {
527 let key_id = format!("v{counter}");
528 *counter = counter.wrapping_add(1);
529 let attr_type = graph
530 .vertex_attributes(name)
531 .and_then(|v| v.first())
532 .map_or("string", attr_type_str);
533 writeln!(
534 writer,
535 " <key id=\"{key_id}\" for=\"node\" attr.name=\"{}\" attr.type=\"{attr_type}\"/>",
536 xml_escape(name)
537 )?;
538 map.insert(name.to_owned(), key_id);
539 }
540 Ok(map)
541}
542
543fn write_edge_keys<W: Write>(
544 graph: &Graph,
545 names: &[&str],
546 counter: &mut u32,
547 writer: &mut W,
548) -> IgraphResult<HashMap<String, String>> {
549 let mut map = HashMap::new();
550 for &name in names {
551 let key_id = format!("e{counter}");
552 *counter = counter.wrapping_add(1);
553 let attr_type = graph
554 .edge_attributes(name)
555 .and_then(|v| v.first())
556 .map_or("string", attr_type_str);
557 writeln!(
558 writer,
559 " <key id=\"{key_id}\" for=\"edge\" attr.name=\"{}\" attr.type=\"{attr_type}\"/>",
560 xml_escape(name)
561 )?;
562 map.insert(name.to_owned(), key_id);
563 }
564 Ok(map)
565}
566
567fn write_graph_data<W: Write>(
568 graph: &Graph,
569 names: &[&str],
570 key_map: &HashMap<String, String>,
571 writer: &mut W,
572) -> IgraphResult<()> {
573 for &name in names {
574 if let (Some(key_id), Some(val)) = (key_map.get(name), graph.graph_attribute(name)) {
575 writeln!(
576 writer,
577 " <data key=\"{key_id}\">{}</data>",
578 xml_escape(&val.to_string())
579 )?;
580 }
581 }
582 Ok(())
583}
584
585fn write_nodes<W: Write>(
586 graph: &Graph,
587 attr_names: &[&str],
588 key_map: &HashMap<String, String>,
589 labels: Option<&[String]>,
590 writer: &mut W,
591) -> IgraphResult<()> {
592 for v in 0..graph.vcount() {
593 let node_id = vertex_id(v, labels);
594 let has_data = attr_names
595 .iter()
596 .any(|name| graph.vertex_attribute(name, v).is_some());
597 if has_data {
598 writeln!(writer, " <node id=\"{}\">", xml_escape(&node_id))?;
599 for &name in attr_names {
600 if let (Some(key_id), Some(val)) =
601 (key_map.get(name), graph.vertex_attribute(name, v))
602 {
603 writeln!(
604 writer,
605 " <data key=\"{key_id}\">{}</data>",
606 xml_escape(&val.to_string())
607 )?;
608 }
609 }
610 writeln!(writer, " </node>")?;
611 } else {
612 writeln!(writer, " <node id=\"{}\"/>", xml_escape(&node_id))?;
613 }
614 }
615 Ok(())
616}
617
618fn write_edges<W: Write>(
619 graph: &Graph,
620 attr_names: &[&str],
621 key_map: &HashMap<String, String>,
622 labels: Option<&[String]>,
623 writer: &mut W,
624) -> IgraphResult<()> {
625 for eid in 0..graph.ecount() {
626 let eid_u32 = u32::try_from(eid).map_err(|_| IgraphError::Internal("edge id overflow"))?;
627 let (from, to) = graph.edge(eid_u32)?;
628 let src_id = vertex_id(from, labels);
629 let tgt_id = vertex_id(to, labels);
630 let has_data = attr_names
631 .iter()
632 .any(|name| graph.edge_attribute(name, eid_u32).is_some());
633 if has_data {
634 writeln!(
635 writer,
636 " <edge source=\"{}\" target=\"{}\">",
637 xml_escape(&src_id),
638 xml_escape(&tgt_id)
639 )?;
640 for &name in attr_names {
641 if let (Some(key_id), Some(val)) =
642 (key_map.get(name), graph.edge_attribute(name, eid_u32))
643 {
644 writeln!(
645 writer,
646 " <data key=\"{key_id}\">{}</data>",
647 xml_escape(&val.to_string())
648 )?;
649 }
650 }
651 writeln!(writer, " </edge>")?;
652 } else {
653 writeln!(
654 writer,
655 " <edge source=\"{}\" target=\"{}\"/>",
656 xml_escape(&src_id),
657 xml_escape(&tgt_id)
658 )?;
659 }
660 }
661 Ok(())
662}
663
664fn attr_type_str(val: &AttributeValue) -> &'static str {
665 match val {
666 AttributeValue::Numeric(_) => "double",
667 AttributeValue::Boolean(_) => "boolean",
668 AttributeValue::String(_) => "string",
669 }
670}
671
672fn vertex_id(v: u32, labels: Option<&[String]>) -> String {
673 match labels {
674 Some(l) => l[v as usize].clone(),
675 None => format!("n{v}"),
676 }
677}
678
679fn xml_escape(s: &str) -> String {
680 let mut out = String::with_capacity(s.len());
681 for c in s.chars() {
682 match c {
683 '&' => out.push_str("&"),
684 '<' => out.push_str("<"),
685 '>' => out.push_str(">"),
686 '"' => out.push_str("""),
687 '\'' => out.push_str("'"),
688 _ => out.push(c),
689 }
690 }
691 out
692}
693
694#[cfg(test)]
695mod tests {
696 use super::*;
697
698 #[test]
699 fn test_basic_undirected() {
700 let mut g = Graph::with_vertices(3);
701 g.add_edge(0, 1).unwrap();
702 g.add_edge(1, 2).unwrap();
703
704 let mut buf = Vec::new();
705 write_graphml(&g, None, &mut buf).unwrap();
706 let s = String::from_utf8(buf).unwrap();
707
708 assert!(s.contains("<?xml version=\"1.0\""));
709 assert!(s.contains("edgedefault=\"undirected\""));
710 assert!(s.contains("n0"));
711 assert!(s.contains("n1"));
712 assert!(s.contains("n2"));
713 }
714
715 #[test]
716 fn test_directed() {
717 let mut g = Graph::new(2, true).unwrap();
718 g.add_edge(0, 1).unwrap();
719
720 let mut buf = Vec::new();
721 write_graphml(&g, None, &mut buf).unwrap();
722 let s = String::from_utf8(buf).unwrap();
723
724 assert!(s.contains("edgedefault=\"directed\""));
725 }
726
727 #[test]
728 fn test_with_labels() {
729 let mut g = Graph::with_vertices(2);
730 g.add_edge(0, 1).unwrap();
731
732 let labels = vec!["Alice".to_string(), "Bob".to_string()];
733 let mut buf = Vec::new();
734 write_graphml(&g, Some(&labels), &mut buf).unwrap();
735 let s = String::from_utf8(buf).unwrap();
736
737 assert!(s.contains("Alice"));
738 assert!(s.contains("Bob"));
739 }
740
741 #[test]
742 fn test_xml_escaping() {
743 let mut g = Graph::with_vertices(2);
744 g.add_edge(0, 1).unwrap();
745
746 let labels = vec!["A&B".to_string(), "C<D".to_string()];
747 let mut buf = Vec::new();
748 write_graphml(&g, Some(&labels), &mut buf).unwrap();
749 let s = String::from_utf8(buf).unwrap();
750
751 assert!(s.contains("A&B"));
752 assert!(s.contains("C<D"));
753 }
754
755 #[test]
756 fn test_empty_graph() {
757 let g = Graph::with_vertices(0);
758
759 let mut buf = Vec::new();
760 write_graphml(&g, None, &mut buf).unwrap();
761 let s = String::from_utf8(buf).unwrap();
762
763 assert!(s.contains("<graph id=\"G\""));
764 assert!(s.contains("</graph>"));
765 }
766
767 #[test]
768 fn test_self_loop() {
769 let mut g = Graph::with_vertices(2);
770 g.add_edge(0, 0).unwrap();
771
772 let mut buf = Vec::new();
773 write_graphml(&g, None, &mut buf).unwrap();
774 let s = String::from_utf8(buf).unwrap();
775
776 assert!(s.contains("source=\"n0\" target=\"n0\""));
777 }
778
779 #[test]
780 fn test_labels_mismatch_error() {
781 let g = Graph::with_vertices(3);
782 let labels = vec!["A".to_string()];
783 let mut buf = Vec::new();
784 assert!(write_graphml(&g, Some(&labels), &mut buf).is_err());
785 }
786
787 #[test]
788 fn read_basic_undirected() {
789 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
790<graphml xmlns="http://graphml.graphdrawing.org/xmlns">
791 <graph id="G" edgedefault="undirected">
792 <node id="n0"/>
793 <node id="n1"/>
794 <node id="n2"/>
795 <edge source="n0" target="n1"/>
796 <edge source="n1" target="n2"/>
797 </graph>
798</graphml>"#;
799 let result = read_graphml(xml.as_bytes()).unwrap();
800 assert_eq!(result.graph.vcount(), 3);
801 assert_eq!(result.graph.ecount(), 2);
802 assert!(!result.graph.is_directed());
803 assert_eq!(result.labels, vec!["n0", "n1", "n2"]);
804 }
805
806 #[test]
807 fn read_directed() {
808 let xml = r#"<graphml>
809 <graph edgedefault="directed">
810 <node id="a"/>
811 <node id="b"/>
812 <edge source="a" target="b"/>
813 </graph>
814</graphml>"#;
815 let result = read_graphml(xml.as_bytes()).unwrap();
816 assert!(result.graph.is_directed());
817 }
818
819 #[test]
820 fn read_empty_graph() {
821 let xml = r#"<graphml><graph edgedefault="undirected"></graph></graphml>"#;
822 let result = read_graphml(xml.as_bytes()).unwrap();
823 assert_eq!(result.graph.vcount(), 0);
824 assert_eq!(result.graph.ecount(), 0);
825 }
826
827 #[test]
828 fn read_unknown_node_in_edge_is_error() {
829 let xml = r#"<graphml>
830 <graph edgedefault="undirected">
831 <node id="a"/>
832 <edge source="a" target="b"/>
833 </graph>
834</graphml>"#;
835 assert!(read_graphml(xml.as_bytes()).is_err());
836 }
837
838 #[test]
839 fn read_no_graph_element_is_error() {
840 let xml = r#"<graphml><key id="k"/></graphml>"#;
841 assert!(read_graphml(xml.as_bytes()).is_err());
842 }
843
844 #[test]
845 fn roundtrip_write_then_read() {
846 let mut g = Graph::new(4, true).unwrap();
847 g.add_edge(0, 1).unwrap();
848 g.add_edge(1, 2).unwrap();
849 g.add_edge(2, 3).unwrap();
850 g.add_edge(3, 0).unwrap();
851
852 let mut buf = Vec::new();
853 write_graphml(&g, None, &mut buf).unwrap();
854
855 let result = read_graphml(buf.as_slice()).unwrap();
856 assert_eq!(result.graph.vcount(), 4);
857 assert_eq!(result.graph.ecount(), 4);
858 assert!(result.graph.is_directed());
859 }
860
861 #[test]
862 fn read_with_attributes() {
863 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
864<graphml xmlns="http://graphml.graphdrawing.org/xmlns">
865 <key id="d0" for="node" attr.name="label" attr.type="string"/>
866 <key id="d1" for="edge" attr.name="weight" attr.type="double"/>
867 <key id="d2" for="graph" attr.name="name" attr.type="string"/>
868 <graph id="G" edgedefault="undirected">
869 <data key="d2">TestGraph</data>
870 <node id="n0">
871 <data key="d0">Alice</data>
872 </node>
873 <node id="n1">
874 <data key="d0">Bob</data>
875 </node>
876 <edge source="n0" target="n1">
877 <data key="d1">2.5</data>
878 </edge>
879 </graph>
880</graphml>"#;
881 let result = read_graphml(xml.as_bytes()).unwrap();
882 assert_eq!(result.graph.vcount(), 2);
883 assert_eq!(result.graph.ecount(), 1);
884
885 assert_eq!(
886 result
887 .graph
888 .vertex_attribute("label", 0)
889 .and_then(|v| v.as_str()),
890 Some("Alice"),
891 );
892 assert_eq!(
893 result
894 .graph
895 .vertex_attribute("label", 1)
896 .and_then(|v| v.as_str()),
897 Some("Bob"),
898 );
899
900 let w = result
901 .graph
902 .edge_attribute("weight", 0)
903 .and_then(AttributeValue::as_f64);
904 assert!((w.unwrap() - 2.5).abs() < f64::EPSILON);
905
906 assert_eq!(
907 result
908 .graph
909 .graph_attribute("name")
910 .and_then(|v| v.as_str()),
911 Some("TestGraph"),
912 );
913 }
914
915 #[test]
916 fn write_with_attributes() {
917 let mut g = Graph::from_edges(&[(0, 1)], false, None).unwrap();
918 g.set_vertex_attribute("color", 0, "red".into()).unwrap();
919 g.set_vertex_attribute("color", 1, "blue".into()).unwrap();
920 g.set_edge_attribute("weight", 0, 3.0.into()).unwrap();
921 g.set_graph_attribute("title", "test".into());
922
923 let mut buf = Vec::new();
924 write_graphml(&g, None, &mut buf).unwrap();
925 let s = String::from_utf8(buf).unwrap();
926
927 assert!(s.contains("attr.name=\"color\""));
928 assert!(s.contains("attr.name=\"weight\""));
929 assert!(s.contains("attr.name=\"title\""));
930 assert!(s.contains("<data key="));
931 assert!(s.contains("red"));
932 assert!(s.contains("blue"));
933 assert!(s.contains("test"));
934 }
935
936 #[test]
937 fn roundtrip_with_attributes() {
938 let mut g = Graph::from_edges(&[(0, 1), (1, 2)], false, None).unwrap();
939 g.set_vertex_attribute("label", 0, "A".into()).unwrap();
940 g.set_vertex_attribute("label", 1, "B".into()).unwrap();
941 g.set_vertex_attribute("label", 2, "C".into()).unwrap();
942 g.set_edge_attribute("weight", 0, 1.5.into()).unwrap();
943 g.set_edge_attribute("weight", 1, 2.5.into()).unwrap();
944 g.set_graph_attribute("name", "roundtrip_test".into());
945
946 let mut buf = Vec::new();
947 write_graphml(&g, None, &mut buf).unwrap();
948
949 let result = read_graphml(buf.as_slice()).unwrap();
950 assert_eq!(result.graph.vcount(), 3);
951 assert_eq!(result.graph.ecount(), 2);
952 assert_eq!(
953 result
954 .graph
955 .vertex_attribute("label", 0)
956 .and_then(|v| v.as_str()),
957 Some("A"),
958 );
959 assert_eq!(
960 result
961 .graph
962 .graph_attribute("name")
963 .and_then(|v| v.as_str()),
964 Some("roundtrip_test"),
965 );
966 }
967
968 #[test]
969 fn read_ignores_extra_elements() {
970 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
971<graphml xmlns="http://graphml.graphdrawing.org/xmlns">
972 <key id="d0" for="node" attr.name="label" attr.type="string"/>
973 <graph id="G" edgedefault="undirected">
974 <node id="n0">
975 <data key="d0">Alice</data>
976 </node>
977 <node id="n1">
978 <data key="d0">Bob</data>
979 </node>
980 <edge source="n0" target="n1">
981 <data key="d1">1.5</data>
982 </edge>
983 </graph>
984</graphml>"#;
985 let result = read_graphml(xml.as_bytes()).unwrap();
986 assert_eq!(result.graph.vcount(), 2);
987 assert_eq!(result.graph.ecount(), 1);
988 assert_eq!(
989 result
990 .graph
991 .vertex_attribute("label", 0)
992 .and_then(|v| v.as_str()),
993 Some("Alice"),
994 );
995 }
996
997 #[test]
998 fn read_boolean_attribute() {
999 let xml = r#"<graphml>
1000 <key id="d0" for="node" attr.name="active" attr.type="boolean"/>
1001 <graph edgedefault="undirected">
1002 <node id="n0"><data key="d0">true</data></node>
1003 <node id="n1"><data key="d0">false</data></node>
1004 </graph>
1005</graphml>"#;
1006 let result = read_graphml(xml.as_bytes()).unwrap();
1007 assert_eq!(
1008 result
1009 .graph
1010 .vertex_attribute("active", 0)
1011 .and_then(AttributeValue::as_bool),
1012 Some(true),
1013 );
1014 assert_eq!(
1015 result
1016 .graph
1017 .vertex_attribute("active", 1)
1018 .and_then(AttributeValue::as_bool),
1019 Some(false),
1020 );
1021 }
1022}