Skip to main content

graphannis_core/graph/serialization/
graphml.rs

1use crate::{
2    annostorage::{Match, ValueSearch},
3    errors::{GraphAnnisCoreError, Result},
4    graph::{
5        ANNIS_NS, Graph, NODE_NAME, NODE_NAME_KEY, NODE_TYPE, NODE_TYPE_KEY,
6        update::{GraphUpdate, UpdateEvent},
7    },
8    types::{AnnoKey, Annotation, Component, ComponentType, Edge},
9    util::{join_qname, split_qname},
10};
11use itertools::Itertools;
12use quick_xml::{
13    Reader, Writer,
14    events::{
15        BytesCData, BytesDecl, BytesEnd, BytesStart, BytesText, Event, attributes::Attributes,
16    },
17};
18use std::{
19    cmp::Ordering,
20    collections::{BTreeMap, BTreeSet, HashMap},
21    io::{BufReader, BufWriter, Read, Write},
22    str::FromStr,
23};
24
25fn write_annotation_keys<CT: ComponentType, W: std::io::Write>(
26    graph: &Graph<CT>,
27    has_graph_configuration: bool,
28    sorted: bool,
29    writer: &mut Writer<W>,
30) -> Result<BTreeMap<AnnoKey, String>> {
31    let mut key_id_mapping = BTreeMap::new();
32    let mut id_counter = 0;
33
34    if has_graph_configuration {
35        let new_id = format!("k{}", id_counter);
36        id_counter += 1;
37
38        let mut key_start = BytesStart::new("key");
39        key_start.push_attribute(("id", new_id.as_str()));
40        key_start.push_attribute(("for", "graph"));
41        key_start.push_attribute(("attr.name", "configuration"));
42        key_start.push_attribute(("attr.type", "string"));
43
44        writer.write_event(Event::Empty(key_start))?;
45    }
46
47    // Create node annotation keys
48    let mut anno_keys = graph.get_node_annos().annotation_keys()?;
49    if sorted {
50        anno_keys.sort_unstable();
51    }
52    for key in anno_keys {
53        if (key.ns != ANNIS_NS || key.name != NODE_NAME) && !key_id_mapping.contains_key(&key) {
54            let new_id = format!("k{}", id_counter);
55            id_counter += 1;
56
57            let qname = join_qname(&key.ns, &key.name);
58
59            let mut key_start = BytesStart::new("key");
60            key_start.push_attribute(("id", new_id.as_str()));
61            key_start.push_attribute(("for", "node"));
62            key_start.push_attribute(("attr.name", qname.as_str()));
63            key_start.push_attribute(("attr.type", "string"));
64
65            writer.write_event(Event::Empty(key_start))?;
66
67            key_id_mapping.insert(key, new_id);
68        }
69    }
70
71    // Create edge annotation keys for all components, but skip auto-generated ones
72    let autogenerated_components: BTreeSet<Component<CT>> =
73        CT::update_graph_index_components(graph)
74            .into_iter()
75            .collect();
76    let mut all_components = graph.get_all_components(None, None);
77    if sorted {
78        all_components.sort_unstable();
79    }
80    for c in all_components {
81        if !autogenerated_components.contains(&c)
82            && let Some(gs) = graph.get_graphstorage(&c)
83        {
84            for key in gs.get_anno_storage().annotation_keys()? {
85                #[allow(clippy::map_entry)]
86                if !key_id_mapping.contains_key(&key) {
87                    let new_id = format!("k{}", id_counter);
88                    id_counter += 1;
89
90                    let qname = join_qname(&key.ns, &key.name);
91
92                    let mut key_start = BytesStart::new("key");
93                    key_start.push_attribute(("id", new_id.as_str()));
94                    key_start.push_attribute(("for", "node"));
95                    key_start.push_attribute(("attr.name", qname.as_str()));
96                    key_start.push_attribute(("attr.type", "string"));
97
98                    writer.write_event(Event::Empty(key_start))?;
99
100                    key_id_mapping.insert(key, new_id);
101                }
102            }
103        }
104    }
105
106    Ok(key_id_mapping)
107}
108
109fn write_data<W: std::io::Write>(
110    anno: Annotation,
111    writer: &mut Writer<W>,
112    key_id_mapping: &BTreeMap<AnnoKey, String>,
113) -> Result<()> {
114    let mut data_start = BytesStart::new("data");
115
116    let key_id = key_id_mapping
117        .get(&anno.key)
118        .ok_or_else(|| GraphAnnisCoreError::GraphMLMissingAnnotationKey(anno.key.clone()))?;
119
120    data_start.push_attribute(("key", key_id.as_str()));
121    writer.write_event(Event::Start(data_start))?;
122    // Add the annotation value as internal text node
123    writer.write_event(Event::Text(BytesText::new(&anno.val)))?;
124    writer.write_event(Event::End(BytesEnd::new("data")))?;
125
126    Ok(())
127}
128
129fn compare_results<T: Ord>(a: &Result<T>, b: &Result<T>) -> Ordering {
130    if let (Ok(a), Ok(b)) = (a, b) {
131        a.cmp(b)
132    } else if a.is_err() {
133        Ordering::Less
134    } else if b.is_err() {
135        Ordering::Greater
136    } else {
137        // Treat two errors as equal
138        Ordering::Equal
139    }
140}
141
142fn write_nodes<CT: ComponentType, W: std::io::Write>(
143    graph: &Graph<CT>,
144    writer: &mut Writer<W>,
145    sorted: bool,
146    key_id_mapping: &BTreeMap<AnnoKey, String>,
147) -> Result<()> {
148    let base_node_iterator =
149        graph
150            .get_node_annos()
151            .exact_anno_search(Some(ANNIS_NS), NODE_TYPE, ValueSearch::Any);
152    let node_iterator: Box<dyn Iterator<Item = Result<Match>>> = if sorted {
153        let it = base_node_iterator.sorted_unstable_by(compare_results);
154        Box::new(it)
155    } else {
156        Box::new(base_node_iterator)
157    };
158
159    for m in node_iterator {
160        let m = m?;
161        let mut node_start = BytesStart::new("node");
162
163        if let Some(id) = graph
164            .get_node_annos()
165            .get_value_for_item(&m.node, &NODE_NAME_KEY)?
166        {
167            node_start.push_attribute(("id", id.as_ref()));
168            let mut node_annotations = graph.get_node_annos().get_annotations_for_item(&m.node)?;
169            if node_annotations.is_empty() {
170                // Write an empty XML element without child nodes
171                writer.write_event(Event::Empty(node_start))?;
172            } else {
173                writer.write_event(Event::Start(node_start))?;
174                // Write all annotations of the node as "data" element, but sort
175                // them using the internal annotation key (k0, k1, k2, etc.)
176                node_annotations.sort_unstable_by_key(|anno| {
177                    key_id_mapping
178                        .get(&anno.key)
179                        .map(|internal_key| internal_key.as_str())
180                        .unwrap_or("")
181                });
182
183                for anno in node_annotations {
184                    if anno.key.ns != ANNIS_NS || anno.key.name != NODE_NAME {
185                        write_data(anno, writer, key_id_mapping)?;
186                    }
187                }
188                writer.write_event(Event::End(BytesEnd::new("node")))?;
189            }
190        }
191    }
192    Ok(())
193}
194
195fn write_edges<CT: ComponentType, W: std::io::Write>(
196    graph: &Graph<CT>,
197    writer: &mut Writer<W>,
198    sorted: bool,
199    key_id_mapping: &BTreeMap<AnnoKey, String>,
200) -> Result<()> {
201    let mut edge_counter = 0;
202    let autogenerated_components: BTreeSet<Component<CT>> =
203        CT::update_graph_index_components(graph)
204            .into_iter()
205            .collect();
206
207    let mut all_components = graph.get_all_components(None, None);
208    if sorted {
209        all_components.sort_unstable();
210    }
211
212    for c in all_components {
213        // Create edge annotation keys for all components, but skip auto-generated ones
214        if !autogenerated_components.contains(&c)
215            && let Some(gs) = graph.get_graphstorage(&c)
216        {
217            let source_nodes_iterator = if sorted {
218                Box::new(gs.source_nodes().sorted_unstable_by(compare_results))
219            } else {
220                gs.source_nodes()
221            };
222            for source in source_nodes_iterator {
223                let source = source?;
224                if let Some(source_id) = graph
225                    .get_node_annos()
226                    .get_value_for_item(&source, &NODE_NAME_KEY)?
227                {
228                    let target_nodes_iterator = if sorted {
229                        Box::new(
230                            gs.get_outgoing_edges(source)
231                                .sorted_unstable_by(compare_results),
232                        )
233                    } else {
234                        gs.get_outgoing_edges(source)
235                    };
236                    for target in target_nodes_iterator {
237                        let target = target?;
238                        if let Some(target_id) = graph
239                            .get_node_annos()
240                            .get_value_for_item(&target, &NODE_NAME_KEY)?
241                        {
242                            let edge = Edge { source, target };
243
244                            let mut edge_id = edge_counter.to_string();
245                            edge_counter += 1;
246                            edge_id.insert(0, 'e');
247
248                            let mut edge_start = BytesStart::new("edge");
249                            edge_start.push_attribute(("id", edge_id.as_str()));
250                            edge_start.push_attribute(("source", source_id.as_ref()));
251                            edge_start.push_attribute(("target", target_id.as_ref()));
252                            // Use the "label" attribute as component type. This is consistent with how Neo4j interprets this non-standard attribute
253                            edge_start.push_attribute(("label", c.to_string().as_ref()));
254
255                            writer.write_event(Event::Start(edge_start))?;
256
257                            // Write all annotations of the node as "data" element, but sort
258                            // them using the internal annotation key (k0, k1, k2, etc.)
259                            let mut edge_annotations =
260                                gs.get_anno_storage().get_annotations_for_item(&edge)?;
261                            edge_annotations.sort_unstable_by_key(|anno| {
262                                key_id_mapping
263                                    .get(&anno.key)
264                                    .map(|internal_key| internal_key.as_str())
265                                    .unwrap_or("")
266                            });
267                            for anno in edge_annotations {
268                                write_data(anno, writer, key_id_mapping)?;
269                            }
270                            writer.write_event(Event::End(BytesEnd::new("edge")))?;
271                        }
272                    }
273                }
274            }
275        }
276    }
277    Ok(())
278}
279
280pub fn export<CT: ComponentType, W: std::io::Write, F>(
281    graph: &Graph<CT>,
282    graph_configuration: Option<&str>,
283    output: W,
284    progress_callback: F,
285) -> Result<()>
286where
287    F: Fn(&str),
288{
289    // Always buffer the output
290    let output = BufWriter::new(output);
291    let mut writer = Writer::new_with_indent(output, b' ', 4);
292
293    // Add XML declaration
294    let xml_decl = BytesDecl::new("1.0", Some("UTF-8"), None);
295    writer.write_event(Event::Decl(xml_decl))?;
296
297    // Always write the root element
298    writer.write_event(Event::Start(BytesStart::new("graphml")))?;
299
300    // Define all valid annotation ns/name pairs
301    progress_callback("exporting all available annotation keys");
302    let key_id_mapping =
303        write_annotation_keys(graph, graph_configuration.is_some(), false, &mut writer)?;
304
305    // We are writing a single graph
306    let mut graph_start = BytesStart::new("graph");
307    graph_start.push_attribute(("edgedefault", "directed"));
308    // Add parse helper information to allow more efficient parsing
309    graph_start.push_attribute(("parse.order", "nodesfirst"));
310    graph_start.push_attribute(("parse.nodeids", "free"));
311    graph_start.push_attribute(("parse.edgeids", "canonical"));
312
313    writer.write_event(Event::Start(graph_start))?;
314
315    // If graph configuration is given, add it as data element to the graph
316    if let Some(config) = graph_configuration {
317        let mut data_start = BytesStart::new("data");
318        // This is always the first key ID
319        data_start.push_attribute(("key", "k0"));
320        writer.write_event(Event::Start(data_start))?;
321        // Add the annotation value as internal text node
322        writer.write_event(Event::CData(BytesCData::new(config)))?;
323        writer.write_event(Event::End(BytesEnd::new("data")))?;
324    }
325
326    // Write out all nodes
327    progress_callback("exporting nodes");
328    write_nodes(graph, &mut writer, false, &key_id_mapping)?;
329
330    // Write out all edges
331    progress_callback("exporting edges");
332    write_edges(graph, &mut writer, false, &key_id_mapping)?;
333
334    writer.write_event(Event::End(BytesEnd::new("graph")))?;
335    writer.write_event(Event::End(BytesEnd::new("graphml")))?;
336
337    // Make sure to flush the buffered writer
338    writer.into_inner().flush()?;
339
340    Ok(())
341}
342
343/// Export the GraphML file and ensure a stable order of the XML elements.
344///
345/// This is slower than [`export`] but can e.g. be used in tests where the
346/// output should always be the same.
347pub fn export_stable_order<CT: ComponentType, W: std::io::Write, F>(
348    graph: &Graph<CT>,
349    graph_configuration: Option<&str>,
350    output: W,
351    progress_callback: F,
352) -> Result<()>
353where
354    F: Fn(&str),
355{
356    // Always buffer the output
357    let output = BufWriter::new(output);
358    let mut writer = Writer::new_with_indent(output, b' ', 4);
359
360    // Add XML declaration
361    let xml_decl = BytesDecl::new("1.0", Some("UTF-8"), None);
362    writer.write_event(Event::Decl(xml_decl))?;
363
364    // Always write the root element
365    writer.write_event(Event::Start(BytesStart::new("graphml")))?;
366
367    // Define all valid annotation ns/name pairs
368    progress_callback("exporting all available annotation keys");
369    let key_id_mapping =
370        write_annotation_keys(graph, graph_configuration.is_some(), true, &mut writer)?;
371
372    // We are writing a single graph
373    let mut graph_start = BytesStart::new("graph");
374    graph_start.push_attribute(("edgedefault", "directed"));
375    // Add parse helper information to allow more efficient parsing
376    graph_start.push_attribute(("parse.order", "nodesfirst"));
377    graph_start.push_attribute(("parse.nodeids", "free"));
378    graph_start.push_attribute(("parse.edgeids", "canonical"));
379
380    writer.write_event(Event::Start(graph_start))?;
381
382    // If graph configuration is given, add it as data element to the graph
383    if let Some(config) = graph_configuration {
384        let mut data_start = BytesStart::new("data");
385        // This is always the first key ID
386        data_start.push_attribute(("key", "k0"));
387        writer.write_event(Event::Start(data_start))?;
388        // Add the annotation value as internal text node
389        writer.write_event(Event::CData(BytesCData::new(config)))?;
390        writer.write_event(Event::End(BytesEnd::new("data")))?;
391    }
392
393    // Write out all nodes
394    progress_callback("exporting nodes");
395    write_nodes(graph, &mut writer, true, &key_id_mapping)?;
396
397    // Write out all edges
398    progress_callback("exporting edges");
399    write_edges(graph, &mut writer, true, &key_id_mapping)?;
400
401    writer.write_event(Event::End(BytesEnd::new("graph")))?;
402    writer.write_event(Event::End(BytesEnd::new("graphml")))?;
403
404    // Make sure to flush the buffered writer
405    writer.into_inner().flush()?;
406
407    Ok(())
408}
409
410fn add_annotation_key(keys: &mut BTreeMap<String, AnnoKey>, attributes: Attributes) -> Result<()> {
411    // resolve the ID to the fully qualified annotation name
412    let mut id: Option<String> = None;
413    let mut anno_key: Option<AnnoKey> = None;
414
415    for att in attributes {
416        let att = att?;
417
418        let att_value = String::from_utf8_lossy(&att.value);
419
420        match att.key.0 {
421            b"id" => {
422                id = Some(att_value.to_string());
423            }
424            b"attr.name" => {
425                let (ns, name) = split_qname(att_value.as_ref());
426                anno_key = Some(AnnoKey {
427                    ns: ns.unwrap_or("").into(),
428                    name: name.into(),
429                });
430            }
431            _ => {}
432        }
433    }
434
435    if let (Some(id), Some(anno_key)) = (id, anno_key) {
436        keys.insert(id, anno_key);
437    }
438    Ok(())
439}
440
441fn add_node(
442    node_updates: &mut GraphUpdate,
443    current_node_id: &Option<String>,
444    data: &mut HashMap<AnnoKey, String>,
445) -> Result<()> {
446    if let Some(node_name) = current_node_id {
447        // Insert graph update for node
448        let node_type = data
449            .remove(&NODE_TYPE_KEY)
450            .unwrap_or_else(|| "node".to_string());
451        node_updates.add_event(UpdateEvent::AddNode {
452            node_name: node_name.clone(),
453            node_type,
454        })?;
455        // Add all remaining data entries as annotations
456        for (key, value) in data.drain() {
457            node_updates.add_event(UpdateEvent::AddNodeLabel {
458                node_name: node_name.clone(),
459                anno_ns: key.ns,
460                anno_name: key.name,
461                anno_value: value,
462            })?;
463        }
464    }
465    Ok(())
466}
467
468fn add_edge<CT: ComponentType>(
469    edge_updates: &mut GraphUpdate,
470    current_source_id: &Option<String>,
471    current_target_id: &Option<String>,
472    current_component: &Option<String>,
473    data: &mut HashMap<AnnoKey, String>,
474) -> Result<()> {
475    if let (Some(source), Some(target), Some(component)) =
476        (current_source_id, current_target_id, current_component)
477    {
478        // Insert graph update for this edge
479        if let Ok(component) = Component::<CT>::from_str(component) {
480            edge_updates.add_event(UpdateEvent::AddEdge {
481                source_node: source.clone(),
482                target_node: target.clone(),
483                layer: component.layer.clone(),
484                component_type: component.get_type().to_string(),
485                component_name: component.name.clone(),
486            })?;
487
488            // Add all remaining data entries as annotations
489            for (key, value) in data.drain() {
490                edge_updates.add_event(UpdateEvent::AddEdgeLabel {
491                    source_node: source.clone(),
492                    target_node: target.clone(),
493                    layer: component.layer.clone(),
494                    component_type: component.get_type().to_string(),
495                    component_name: component.name.clone(),
496                    anno_ns: key.ns,
497                    anno_name: key.name,
498                    anno_value: value,
499                })?;
500            }
501        }
502    }
503    Ok(())
504}
505
506fn read_graphml<CT: ComponentType, R: std::io::BufRead, F: Fn(&str)>(
507    input: &mut R,
508    node_updates: &mut GraphUpdate,
509    edge_updates: &mut GraphUpdate,
510    progress_callback: &F,
511) -> Result<Option<String>> {
512    let mut reader = Reader::from_reader(input);
513    reader.expand_empty_elements(true);
514
515    let mut keys = BTreeMap::new();
516
517    let mut level = 0;
518    let mut in_graph = false;
519    let mut current_node_id: Option<String> = None;
520    let mut current_data_key: Option<String> = None;
521    let mut current_source_id: Option<String> = None;
522    let mut current_target_id: Option<String> = None;
523    let mut current_component: Option<String> = None;
524    let mut current_data_value: Option<String> = None;
525    let mut data: HashMap<AnnoKey, String> = HashMap::new();
526
527    let mut config = None;
528
529    let mut processed_updates = 0;
530
531    let mut buf = Vec::new();
532    loop {
533        match reader.read_event_into(&mut buf)? {
534            Event::Start(ref e) => {
535                level += 1;
536
537                match e.name().0 {
538                    b"graph" if level == 2 => {
539                        in_graph = true;
540                    }
541                    b"key" if level == 2 => {
542                        add_annotation_key(&mut keys, e.attributes())?;
543                    }
544                    b"node" if in_graph && level == 3 => {
545                        data.clear();
546                        // Get the ID of this node
547                        for att in e.attributes() {
548                            let att = att?;
549                            if att.key.0 == b"id" {
550                                current_node_id =
551                                    Some(String::from_utf8_lossy(&att.value).to_string());
552                            }
553                        }
554                    }
555
556                    b"edge" if in_graph && level == 3 => {
557                        data.clear();
558                        // Get the source and target node IDs
559                        for att in e.attributes() {
560                            let att = att?;
561                            if att.key.0 == b"source" {
562                                current_source_id =
563                                    Some(String::from_utf8_lossy(&att.value).to_string());
564                            } else if att.key.0 == b"target" {
565                                current_target_id =
566                                    Some(String::from_utf8_lossy(&att.value).to_string());
567                            } else if att.key.0 == b"label" {
568                                current_component =
569                                    Some(String::from_utf8_lossy(&att.value).to_string());
570                            }
571                        }
572                    }
573
574                    b"data" => {
575                        for att in e.attributes() {
576                            let att = att?;
577                            if att.key.0 == b"key" {
578                                current_data_key =
579                                    Some(String::from_utf8_lossy(&att.value).to_string());
580                            }
581                        }
582                    }
583                    _ => {}
584                }
585            }
586            Event::Text(t) if in_graph && level == 4 && current_data_key.is_some() => {
587                current_data_value = Some(t.unescape()?.to_string());
588            }
589
590            Event::CData(t) => {
591                if let Some(current_data_key) = &current_data_key
592                    && in_graph
593                    && level == 3
594                    && current_data_key == "k0"
595                {
596                    // This is the configuration content
597                    config = Some(String::from_utf8_lossy(&t).to_string());
598                }
599            }
600            Event::End(ref e) => {
601                match e.name().0 {
602                    b"graph" => {
603                        in_graph = false;
604                    }
605                    b"node" => {
606                        add_node(node_updates, &current_node_id, &mut data)?;
607                        current_node_id = None;
608                        processed_updates += 1;
609                        if processed_updates % 1_000_000 == 0 {
610                            progress_callback(&format!(
611                                "Processed {} GraphML nodes and edges",
612                                processed_updates
613                            ));
614                        }
615                    }
616                    b"edge" => {
617                        add_edge::<CT>(
618                            edge_updates,
619                            &current_source_id,
620                            &current_target_id,
621                            &current_component,
622                            &mut data,
623                        )?;
624                        current_source_id = None;
625                        current_target_id = None;
626                        current_component = None;
627                        processed_updates += 1;
628                        if processed_updates % 1_000_000 == 0 {
629                            progress_callback(&format!(
630                                "Processed {} GraphML nodes and edges",
631                                processed_updates
632                            ));
633                        }
634                    }
635                    b"data" => {
636                        if let Some(current_data_key) = current_data_key
637                            && let Some(anno_key) = keys.get(&current_data_key)
638                        {
639                            // Copy all data attributes into our own map
640                            if let Some(v) = current_data_value.take() {
641                                data.insert(anno_key.clone(), v);
642                            } else {
643                                // If there is an end tag without any text
644                                // data event, the value exists but is
645                                // empty.
646                                data.insert(anno_key.clone(), String::default());
647                            }
648                        }
649
650                        current_data_value = None;
651                        current_data_key = None;
652                    }
653                    _ => {}
654                }
655
656                level -= 1;
657            }
658            Event::Eof => {
659                break;
660            }
661            _ => {}
662        }
663        // Clear the buffer after each event
664        buf.clear();
665    }
666    Ok(config)
667}
668
669pub fn import<CT: ComponentType, R: Read, F>(
670    input: R,
671    disk_based: bool,
672    progress_callback: F,
673) -> Result<(Graph<CT>, Option<String>)>
674where
675    F: Fn(&str),
676{
677    // Always buffer the read operations
678    let mut input = BufReader::new(input);
679    let mut g = Graph::with_default_graphstorages(disk_based)?;
680    let mut updates = GraphUpdate::default();
681    let mut edge_updates = GraphUpdate::default();
682
683    // read in all nodes and edges, collecting annotation keys on the fly
684    progress_callback("reading GraphML");
685    let config = read_graphml::<CT, BufReader<R>, F>(
686        &mut input,
687        &mut updates,
688        &mut edge_updates,
689        &progress_callback,
690    )?;
691
692    // Append all edges updates after the node updates:
693    // edges would not be added if the nodes they are referring do not exist
694    progress_callback("merging generated events");
695    for event in edge_updates.iter()? {
696        let (_, event) = event?;
697        updates.add_event(event)?;
698    }
699
700    progress_callback("applying imported changes");
701    g.apply_update(&mut updates, &progress_callback)?;
702
703    progress_callback("calculating graph statistics");
704    g.calculate_all_statistics()?;
705
706    for c in g.get_all_components(None, None) {
707        progress_callback(&format!("optimizing implementation for component {}", c));
708        g.optimize_gs_impl(&c)?;
709    }
710
711    Ok((g, config))
712}
713
714#[cfg(test)]
715mod tests {
716    use super::*;
717    use crate::{
718        graph::{DEFAULT_NS, GraphUpdate},
719        types::DefaultComponentType,
720    };
721    use pretty_assertions::assert_eq;
722    use std::borrow::Cow;
723
724    const TEST_CONFIG: &str = r#"[some]
725key = "<value>"
726
727[some.another]
728value = "test""#;
729
730    #[test]
731    fn export_graphml() {
732        // Create a sample graph using the simple type
733        let mut u = GraphUpdate::new();
734        u.add_event(UpdateEvent::AddNode {
735            node_name: "first_node".to_string(),
736            node_type: "node".to_string(),
737        })
738        .unwrap();
739        u.add_event(UpdateEvent::AddNode {
740            node_name: "second_node".to_string(),
741            node_type: "node".to_string(),
742        })
743        .unwrap();
744        u.add_event(UpdateEvent::AddNodeLabel {
745            node_name: "first_node".to_string(),
746            anno_ns: DEFAULT_NS.to_string(),
747            anno_name: "an_annotation".to_string(),
748            anno_value: "something <strong>important</strong>".to_string(),
749        })
750        .unwrap();
751
752        u.add_event(UpdateEvent::AddEdge {
753            source_node: "first_node".to_string(),
754            target_node: "second_node".to_string(),
755            component_type: "Edge".to_string(),
756            layer: "some_ns".to_string(),
757            component_name: "test_component".to_string(),
758        })
759        .unwrap();
760
761        let mut g: Graph<DefaultComponentType> = Graph::new(false).unwrap();
762        g.apply_update(&mut u, |_| {}).unwrap();
763
764        // export to GraphML, read generated XML and compare it
765        let mut xml_data: Vec<u8> = Vec::default();
766        export(&g, Some(TEST_CONFIG), &mut xml_data, |_| {}).unwrap();
767        let expected = include_str!("graphml_example.graphml");
768        let actual = String::from_utf8(xml_data).unwrap();
769        assert_eq!(expected, actual);
770    }
771
772    #[test]
773    fn export_graphml_sorted() {
774        // Create a sample graph using the simple type
775        let mut u = GraphUpdate::new();
776
777        u.add_event(UpdateEvent::AddNode {
778            node_name: "1".to_string(),
779            node_type: "node".to_string(),
780        })
781        .unwrap();
782        u.add_event(UpdateEvent::AddNode {
783            node_name: "2".to_string(),
784            node_type: "node".to_string(),
785        })
786        .unwrap();
787        u.add_event(UpdateEvent::AddNodeLabel {
788            node_name: "1".to_string(),
789            anno_ns: DEFAULT_NS.to_string(),
790            anno_name: "an_annotation".to_string(),
791            anno_value: "something".to_string(),
792        })
793        .unwrap();
794
795        u.add_event(UpdateEvent::AddEdge {
796            source_node: "1".to_string(),
797            target_node: "2".to_string(),
798            component_type: "Edge".to_string(),
799            layer: "some_ns".to_string(),
800            component_name: "test_component".to_string(),
801        })
802        .unwrap();
803
804        let mut g: Graph<DefaultComponentType> = Graph::new(false).unwrap();
805        g.apply_update(&mut u, |_| {}).unwrap();
806
807        // export to GraphML, read generated XML and compare it
808        let mut xml_data: Vec<u8> = Vec::default();
809        export_stable_order(&g, Some(TEST_CONFIG), &mut xml_data, |_| {}).unwrap();
810        let expected = include_str!("graphml_example sorted.graphml");
811        let actual = String::from_utf8(xml_data).unwrap();
812        assert_eq!(expected, actual);
813    }
814
815    #[test]
816    fn import_graphml() {
817        let input_xml = std::io::Cursor::new(
818            include_str!("graphml_example.graphml")
819                .as_bytes()
820                .to_owned(),
821        );
822        let (g, config_str) = import(input_xml, false, |_| {}).unwrap();
823
824        // Check that all nodes, edges and annotations have been created
825        let first_node_id = g
826            .node_annos
827            .get_node_id_from_name("first_node")
828            .unwrap()
829            .unwrap();
830        let second_node_id = g
831            .node_annos
832            .get_node_id_from_name("second_node")
833            .unwrap()
834            .unwrap();
835
836        let first_node_annos = g
837            .get_node_annos()
838            .get_annotations_for_item(&first_node_id)
839            .unwrap();
840        assert_eq!(3, first_node_annos.len());
841        assert_eq!(
842            Some(Cow::Borrowed("something <strong>important</strong>")),
843            g.get_node_annos()
844                .get_value_for_item(
845                    &first_node_id,
846                    &AnnoKey {
847                        ns: DEFAULT_NS.into(),
848                        name: "an_annotation".into(),
849                    }
850                )
851                .unwrap()
852        );
853
854        assert_eq!(
855            2,
856            g.get_node_annos()
857                .get_annotations_for_item(&second_node_id)
858                .unwrap()
859                .len()
860        );
861
862        let component = g.get_all_components(Some(DefaultComponentType::Edge), None);
863        assert_eq!(1, component.len());
864        assert_eq!("some_ns", component[0].layer);
865        assert_eq!("test_component", component[0].name);
866
867        let test_gs = g.get_graphstorage_as_ref(&component[0]).unwrap();
868        assert_eq!(
869            Some(1),
870            test_gs.distance(first_node_id, second_node_id).unwrap()
871        );
872
873        assert_eq!(Some(TEST_CONFIG), config_str.as_deref());
874    }
875}