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" => {
539                        if level == 2 {
540                            in_graph = true;
541                        }
542                    }
543                    b"key" => {
544                        if level == 2 {
545                            add_annotation_key(&mut keys, e.attributes())?;
546                        }
547                    }
548                    b"node" => {
549                        if in_graph && level == 3 {
550                            data.clear();
551                            // Get the ID of this node
552                            for att in e.attributes() {
553                                let att = att?;
554                                if att.key.0 == b"id" {
555                                    current_node_id =
556                                        Some(String::from_utf8_lossy(&att.value).to_string());
557                                }
558                            }
559                        }
560                    }
561                    b"edge" => {
562                        if in_graph && level == 3 {
563                            data.clear();
564                            // Get the source and target node IDs
565                            for att in e.attributes() {
566                                let att = att?;
567                                if att.key.0 == b"source" {
568                                    current_source_id =
569                                        Some(String::from_utf8_lossy(&att.value).to_string());
570                                } else if att.key.0 == b"target" {
571                                    current_target_id =
572                                        Some(String::from_utf8_lossy(&att.value).to_string());
573                                } else if att.key.0 == b"label" {
574                                    current_component =
575                                        Some(String::from_utf8_lossy(&att.value).to_string());
576                                }
577                            }
578                        }
579                    }
580                    b"data" => {
581                        for att in e.attributes() {
582                            let att = att?;
583                            if att.key.0 == b"key" {
584                                current_data_key =
585                                    Some(String::from_utf8_lossy(&att.value).to_string());
586                            }
587                        }
588                    }
589                    _ => {}
590                }
591            }
592            Event::Text(t) => {
593                if in_graph && level == 4 && current_data_key.is_some() {
594                    current_data_value = Some(t.unescape()?.to_string());
595                }
596            }
597            Event::CData(t) => {
598                if let Some(current_data_key) = &current_data_key
599                    && in_graph
600                    && level == 3
601                    && current_data_key == "k0"
602                {
603                    // This is the configuration content
604                    config = Some(String::from_utf8_lossy(&t).to_string());
605                }
606            }
607            Event::End(ref e) => {
608                match e.name().0 {
609                    b"graph" => {
610                        in_graph = false;
611                    }
612                    b"node" => {
613                        add_node(node_updates, &current_node_id, &mut data)?;
614                        current_node_id = None;
615                        processed_updates += 1;
616                        if processed_updates % 1_000_000 == 0 {
617                            progress_callback(&format!(
618                                "Processed {} GraphML nodes and edges",
619                                processed_updates
620                            ));
621                        }
622                    }
623                    b"edge" => {
624                        add_edge::<CT>(
625                            edge_updates,
626                            &current_source_id,
627                            &current_target_id,
628                            &current_component,
629                            &mut data,
630                        )?;
631                        current_source_id = None;
632                        current_target_id = None;
633                        current_component = None;
634                        processed_updates += 1;
635                        if processed_updates % 1_000_000 == 0 {
636                            progress_callback(&format!(
637                                "Processed {} GraphML nodes and edges",
638                                processed_updates
639                            ));
640                        }
641                    }
642                    b"data" => {
643                        if let Some(current_data_key) = current_data_key
644                            && let Some(anno_key) = keys.get(&current_data_key)
645                        {
646                            // Copy all data attributes into our own map
647                            if let Some(v) = current_data_value.take() {
648                                data.insert(anno_key.clone(), v);
649                            } else {
650                                // If there is an end tag without any text
651                                // data event, the value exists but is
652                                // empty.
653                                data.insert(anno_key.clone(), String::default());
654                            }
655                        }
656
657                        current_data_value = None;
658                        current_data_key = None;
659                    }
660                    _ => {}
661                }
662
663                level -= 1;
664            }
665            Event::Eof => {
666                break;
667            }
668            _ => {}
669        }
670        // Clear the buffer after each event
671        buf.clear();
672    }
673    Ok(config)
674}
675
676pub fn import<CT: ComponentType, R: Read, F>(
677    input: R,
678    disk_based: bool,
679    progress_callback: F,
680) -> Result<(Graph<CT>, Option<String>)>
681where
682    F: Fn(&str),
683{
684    // Always buffer the read operations
685    let mut input = BufReader::new(input);
686    let mut g = Graph::with_default_graphstorages(disk_based)?;
687    let mut updates = GraphUpdate::default();
688    let mut edge_updates = GraphUpdate::default();
689
690    // read in all nodes and edges, collecting annotation keys on the fly
691    progress_callback("reading GraphML");
692    let config = read_graphml::<CT, BufReader<R>, F>(
693        &mut input,
694        &mut updates,
695        &mut edge_updates,
696        &progress_callback,
697    )?;
698
699    // Append all edges updates after the node updates:
700    // edges would not be added if the nodes they are referring do not exist
701    progress_callback("merging generated events");
702    for event in edge_updates.iter()? {
703        let (_, event) = event?;
704        updates.add_event(event)?;
705    }
706
707    progress_callback("applying imported changes");
708    g.apply_update(&mut updates, &progress_callback)?;
709
710    progress_callback("calculating graph statistics");
711    g.calculate_all_statistics()?;
712
713    for c in g.get_all_components(None, None) {
714        progress_callback(&format!("optimizing implementation for component {}", c));
715        g.optimize_gs_impl(&c)?;
716    }
717
718    Ok((g, config))
719}
720
721#[cfg(test)]
722mod tests {
723    use super::*;
724    use crate::{
725        graph::{DEFAULT_NS, GraphUpdate},
726        types::DefaultComponentType,
727    };
728    use pretty_assertions::assert_eq;
729    use std::borrow::Cow;
730
731    const TEST_CONFIG: &str = r#"[some]
732key = "<value>"
733
734[some.another]
735value = "test""#;
736
737    #[test]
738    fn export_graphml() {
739        // Create a sample graph using the simple type
740        let mut u = GraphUpdate::new();
741        u.add_event(UpdateEvent::AddNode {
742            node_name: "first_node".to_string(),
743            node_type: "node".to_string(),
744        })
745        .unwrap();
746        u.add_event(UpdateEvent::AddNode {
747            node_name: "second_node".to_string(),
748            node_type: "node".to_string(),
749        })
750        .unwrap();
751        u.add_event(UpdateEvent::AddNodeLabel {
752            node_name: "first_node".to_string(),
753            anno_ns: DEFAULT_NS.to_string(),
754            anno_name: "an_annotation".to_string(),
755            anno_value: "something".to_string(),
756        })
757        .unwrap();
758
759        u.add_event(UpdateEvent::AddEdge {
760            source_node: "first_node".to_string(),
761            target_node: "second_node".to_string(),
762            component_type: "Edge".to_string(),
763            layer: "some_ns".to_string(),
764            component_name: "test_component".to_string(),
765        })
766        .unwrap();
767
768        let mut g: Graph<DefaultComponentType> = Graph::new(false).unwrap();
769        g.apply_update(&mut u, |_| {}).unwrap();
770
771        // export to GraphML, read generated XML and compare it
772        let mut xml_data: Vec<u8> = Vec::default();
773        export(&g, Some(TEST_CONFIG), &mut xml_data, |_| {}).unwrap();
774        let expected = include_str!("graphml_example.graphml");
775        let actual = String::from_utf8(xml_data).unwrap();
776        assert_eq!(expected, actual);
777    }
778
779    #[test]
780    fn export_graphml_sorted() {
781        // Create a sample graph using the simple type
782        let mut u = GraphUpdate::new();
783
784        u.add_event(UpdateEvent::AddNode {
785            node_name: "1".to_string(),
786            node_type: "node".to_string(),
787        })
788        .unwrap();
789        u.add_event(UpdateEvent::AddNode {
790            node_name: "2".to_string(),
791            node_type: "node".to_string(),
792        })
793        .unwrap();
794        u.add_event(UpdateEvent::AddNodeLabel {
795            node_name: "1".to_string(),
796            anno_ns: DEFAULT_NS.to_string(),
797            anno_name: "an_annotation".to_string(),
798            anno_value: "something".to_string(),
799        })
800        .unwrap();
801
802        u.add_event(UpdateEvent::AddEdge {
803            source_node: "1".to_string(),
804            target_node: "2".to_string(),
805            component_type: "Edge".to_string(),
806            layer: "some_ns".to_string(),
807            component_name: "test_component".to_string(),
808        })
809        .unwrap();
810
811        let mut g: Graph<DefaultComponentType> = Graph::new(false).unwrap();
812        g.apply_update(&mut u, |_| {}).unwrap();
813
814        // export to GraphML, read generated XML and compare it
815        let mut xml_data: Vec<u8> = Vec::default();
816        export_stable_order(&g, Some(TEST_CONFIG), &mut xml_data, |_| {}).unwrap();
817        let expected = include_str!("graphml_example sorted.graphml");
818        let actual = String::from_utf8(xml_data).unwrap();
819        assert_eq!(expected, actual);
820    }
821
822    #[test]
823    fn import_graphml() {
824        let input_xml = std::io::Cursor::new(
825            include_str!("graphml_example.graphml")
826                .as_bytes()
827                .to_owned(),
828        );
829        let (g, config_str) = import(input_xml, false, |_| {}).unwrap();
830
831        // Check that all nodes, edges and annotations have been created
832        let first_node_id = g
833            .node_annos
834            .get_node_id_from_name("first_node")
835            .unwrap()
836            .unwrap();
837        let second_node_id = g
838            .node_annos
839            .get_node_id_from_name("second_node")
840            .unwrap()
841            .unwrap();
842
843        let first_node_annos = g
844            .get_node_annos()
845            .get_annotations_for_item(&first_node_id)
846            .unwrap();
847        assert_eq!(3, first_node_annos.len());
848        assert_eq!(
849            Some(Cow::Borrowed("something")),
850            g.get_node_annos()
851                .get_value_for_item(
852                    &first_node_id,
853                    &AnnoKey {
854                        ns: DEFAULT_NS.into(),
855                        name: "an_annotation".into(),
856                    }
857                )
858                .unwrap()
859        );
860
861        assert_eq!(
862            2,
863            g.get_node_annos()
864                .get_annotations_for_item(&second_node_id)
865                .unwrap()
866                .len()
867        );
868
869        let component = g.get_all_components(Some(DefaultComponentType::Edge), None);
870        assert_eq!(1, component.len());
871        assert_eq!("some_ns", component[0].layer);
872        assert_eq!("test_component", component[0].name);
873
874        let test_gs = g.get_graphstorage_as_ref(&component[0]).unwrap();
875        assert_eq!(
876            Some(1),
877            test_gs.distance(first_node_id, second_node_id).unwrap()
878        );
879
880        assert_eq!(Some(TEST_CONFIG), config_str.as_deref());
881    }
882}