graphify_export/
graphml.rs1use std::fmt::Write;
4use std::fs;
5use std::path::{Path, PathBuf};
6
7use graphify_core::graph::KnowledgeGraph;
8use tracing::info;
9
10pub fn export_graphml(graph: &KnowledgeGraph, output_dir: &Path) -> anyhow::Result<PathBuf> {
12 let mut xml = String::with_capacity(4096);
13
14 writeln!(xml, r#"<?xml version="1.0" encoding="UTF-8"?>"#)?;
15 writeln!(
16 xml,
17 r#"<graphml xmlns="http://graphml.graphdrawing.org/xmlns""#
18 )?;
19 writeln!(
20 xml,
21 r#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance""#
22 )?;
23 writeln!(
24 xml,
25 r#" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">"#
26 )?;
27
28 writeln!(
29 xml,
30 r#" <key id="label" for="node" attr.name="label" attr.type="string"/>"#
31 )?;
32 writeln!(
33 xml,
34 r#" <key id="node_type" for="node" attr.name="node_type" attr.type="string"/>"#
35 )?;
36 writeln!(
37 xml,
38 r#" <key id="source_file" for="node" attr.name="source_file" attr.type="string"/>"#
39 )?;
40 writeln!(
41 xml,
42 r#" <key id="community" for="node" attr.name="community" attr.type="int"/>"#
43 )?;
44
45 writeln!(
46 xml,
47 r#" <key id="relation" for="edge" attr.name="relation" attr.type="string"/>"#
48 )?;
49 writeln!(
50 xml,
51 r#" <key id="confidence" for="edge" attr.name="confidence" attr.type="string"/>"#
52 )?;
53 writeln!(
54 xml,
55 r#" <key id="confidence_score" for="edge" attr.name="confidence_score" attr.type="double"/>"#
56 )?;
57 writeln!(
58 xml,
59 r#" <key id="weight" for="edge" attr.name="weight" attr.type="double"/>"#
60 )?;
61
62 writeln!(xml, r#" <graph id="G" edgedefault="undirected">"#)?;
63
64 for node in graph.nodes() {
65 writeln!(xml, r#" <node id="{}">"#, xml_escape(&node.id))?;
66 writeln!(
67 xml,
68 r#" <data key="label">{}</data>"#,
69 xml_escape(&node.label)
70 )?;
71 writeln!(
72 xml,
73 r#" <data key="node_type">{}</data>"#,
74 node.node_type
75 )?;
76 writeln!(
77 xml,
78 r#" <data key="source_file">{}</data>"#,
79 xml_escape(&node.source_file)
80 )?;
81 if let Some(c) = node.community {
82 writeln!(xml, r#" <data key="community">{c}</data>"#)?;
83 }
84 writeln!(xml, " </node>")?;
85 }
86
87 for (i, edge) in graph.edges().iter().enumerate() {
88 writeln!(
89 xml,
90 r#" <edge id="e{}" source="{}" target="{}">"#,
91 i,
92 xml_escape(&edge.source),
93 xml_escape(&edge.target)
94 )?;
95 writeln!(
96 xml,
97 r#" <data key="relation">{}</data>"#,
98 xml_escape(&edge.relation)
99 )?;
100 writeln!(
101 xml,
102 r#" <data key="confidence">{}</data>"#,
103 edge.confidence
104 )?;
105 writeln!(
106 xml,
107 r#" <data key="confidence_score">{}</data>"#,
108 edge.confidence_score
109 )?;
110 writeln!(xml, r#" <data key="weight">{}</data>"#, edge.weight)?;
111 writeln!(xml, " </edge>")?;
112 }
113
114 writeln!(xml, " </graph>")?;
115 writeln!(xml, "</graphml>")?;
116
117 fs::create_dir_all(output_dir)?;
118 let path = output_dir.join("graph.graphml");
119 fs::write(&path, &xml)?;
120 info!(path = %path.display(), "exported GraphML");
121 Ok(path)
122}
123
124fn xml_escape(s: &str) -> String {
125 s.replace('&', "&")
126 .replace('<', "<")
127 .replace('>', ">")
128 .replace('"', """)
129 .replace('\'', "'")
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use graphify_core::confidence::Confidence;
136 use graphify_core::graph::KnowledgeGraph;
137 use graphify_core::model::{GraphEdge, GraphNode, NodeType};
138 use std::collections::HashMap;
139
140 fn sample_graph() -> KnowledgeGraph {
141 let mut kg = KnowledgeGraph::new();
142 kg.add_node(GraphNode {
143 id: "a".into(),
144 label: "Node A".into(),
145 source_file: "test.rs".into(),
146 source_location: None,
147 node_type: NodeType::Class,
148 community: Some(0),
149 extra: HashMap::new(),
150 })
151 .unwrap();
152 kg.add_node(GraphNode {
153 id: "b".into(),
154 label: "Node B".into(),
155 source_file: "test.rs".into(),
156 source_location: None,
157 node_type: NodeType::Function,
158 community: None,
159 extra: HashMap::new(),
160 })
161 .unwrap();
162 kg.add_edge(GraphEdge {
163 source: "a".into(),
164 target: "b".into(),
165 relation: "calls".into(),
166 confidence: Confidence::Extracted,
167 confidence_score: 1.0,
168 source_file: "test.rs".into(),
169 source_location: None,
170 weight: 1.0,
171 extra: HashMap::new(),
172 })
173 .unwrap();
174 kg
175 }
176
177 #[test]
178 fn export_graphml_creates_valid_xml() {
179 let dir = tempfile::tempdir().unwrap();
180 let kg = sample_graph();
181 let path = export_graphml(&kg, dir.path()).unwrap();
182 assert!(path.exists());
183
184 let content = std::fs::read_to_string(&path).unwrap();
185 assert!(content.contains("<graphml"));
186 assert!(content.contains(r#"<node id="a">"#));
187 assert!(content.contains(r#"<node id="b">"#));
188 assert!(content.contains(r#"source="a""#));
189 assert!(content.contains("</graphml>"));
190 }
191
192 #[test]
193 fn xml_escape_special_chars() {
194 assert_eq!(xml_escape("<a&b>"), "<a&b>");
195 }
196}