1use std::collections::HashMap;
2
3use petgraph::graph::NodeIndex;
4use quick_xml::events::Event;
5use quick_xml::Reader;
6use thiserror::Error;
7
8use crate::owned::{OwnedClassNode, OwnedGraph};
9use crate::EdgeType;
10
11#[derive(Debug, Error)]
12pub enum Error {
13 #[error("XML: {0}")]
14 Xml(#[from] quick_xml::Error),
15 #[error("JSON for key '{key}': {source}")]
16 Json {
17 key: String,
18 #[source]
19 source: serde_json::Error,
20 },
21 #[error("missing required node attribute '{0}'")]
22 MissingAttr(String),
23 #[error("invalid integer for '{key}': {source}")]
24 ParseInt {
25 key: String,
26 #[source]
27 source: std::num::ParseIntError,
28 },
29 #[error("unknown edge type '{0}'")]
30 UnknownEdgeType(String),
31 #[error("UTF-8: {0}")]
32 Utf8(#[from] std::string::FromUtf8Error),
33}
34
35fn attr_str(e: &quick_xml::events::BytesStart<'_>, name: &[u8]) -> Option<String> {
38 e.attributes()
39 .filter_map(|a| a.ok())
40 .find(|a| a.key.local_name().as_ref() == name)
41 .and_then(|a| String::from_utf8(a.value.into_owned()).ok())
42}
43
44fn parse_edge_type(s: &str) -> Result<EdgeType, Error> {
45 match s {
46 "MethodCall" => Ok(EdgeType::MethodCall),
47 "FieldRef" => Ok(EdgeType::FieldRef),
48 "Inheritance" => Ok(EdgeType::Inheritance),
49 other => Err(Error::UnknownEdgeType(other.to_owned())),
50 }
51}
52
53pub fn read_graphml(xml: &str) -> Result<OwnedGraph, Error> {
55 let mut reader = Reader::from_str(xml);
56
57 let mut graph = OwnedGraph::new();
58 let mut node_index: HashMap<String, NodeIndex> = HashMap::new();
59
60 let mut in_node = false;
62 let mut in_edge = false;
63 let mut in_data = false;
64 let mut current_node_id = String::new();
65 let mut current_edge_source = String::new();
66 let mut current_edge_target = String::new();
67 let mut current_data: HashMap<String, String> = HashMap::new();
68 let mut current_data_key = String::new();
69 let mut pending_edges: Vec<(String, String, String)> = Vec::new();
71
72 loop {
73 match reader.read_event()? {
74 Event::Start(ref e) => match e.name().local_name().as_ref() {
75 b"node" => {
76 in_node = true;
77 current_node_id = attr_str(e, b"id").unwrap_or_default();
78 current_data.clear();
79 }
80 b"edge" => {
81 in_edge = true;
82 current_edge_source = attr_str(e, b"source").unwrap_or_default();
83 current_edge_target = attr_str(e, b"target").unwrap_or_default();
84 current_data.clear();
85 }
86 b"data" => {
87 in_data = true;
88 current_data_key = attr_str(e, b"key").unwrap_or_default();
89 }
90 _ => {}
91 },
92 Event::End(ref e) => match e.name().local_name().as_ref() {
93 b"node" => {
94 let node = OwnedClassNode::from_data(¤t_node_id, ¤t_data)?;
95 let idx = graph.add_node(node);
96 node_index.insert(current_node_id.clone(), idx);
97 in_node = false;
98 }
99 b"edge" => {
100 let type_str = current_data
101 .get("type")
102 .cloned()
103 .unwrap_or_else(|| "MethodCall".to_owned());
104 pending_edges.push((
105 current_edge_source.clone(),
106 current_edge_target.clone(),
107 type_str,
108 ));
109 in_edge = false;
110 }
111 b"data" => {
112 in_data = false;
113 }
114 _ => {}
115 },
116 Event::Text(ref e) => {
117 if in_data && (in_node || in_edge) {
118 if let Ok(text) = e.unescape() {
119 current_data.insert(current_data_key.clone(), text.into_owned());
120 }
121 }
122 }
123 Event::CData(ref e) => {
125 if in_data && (in_node || in_edge) {
126 if let Ok(s) = std::str::from_utf8(e.as_ref()) {
127 current_data.insert(current_data_key.clone(), s.to_owned());
128 }
129 }
130 }
131 Event::Eof => break,
132 _ => {}
133 }
134 }
135
136 for (src, tgt, type_str) in pending_edges {
137 let edge_type = parse_edge_type(&type_str)?;
138 if let (Some(&s), Some(&t)) = (node_index.get(&src), node_index.get(&tgt)) {
139 graph.add_edge(s, t, edge_type);
140 }
141 }
142
143 Ok(graph)
144}
145
146pub fn write_graphml(graph: &OwnedGraph) -> Result<String, Error> {
150 use quick_xml::events::{BytesCData, BytesDecl, BytesEnd, BytesStart, BytesText, Event};
151 use quick_xml::Writer;
152
153 let mut w = Writer::new(Vec::new());
154
155 w.write_event(Event::Decl(BytesDecl::new("1.0", Some("utf-8"), None)))?;
156 w.write_event(Event::Text(BytesText::new("\n")))?;
157
158 let mut graphml = BytesStart::new("graphml");
159 graphml.push_attribute(("xmlns", "http://graphml.graphdrawing.org/xmlns"));
160 w.write_event(Event::Start(graphml))?;
161 w.write_event(Event::Text(BytesText::new("\n")))?;
162
163 let node_keys: &[(&str, &str)] = &[
165 ("name", "string"),
166 ("namespace", "string"),
167 ("lineCount", "int"),
168 ("methodCount", "int"),
169 ("methodConnectivity", "string"),
170 ("halstead_eta1", "int"),
171 ("halstead_eta2", "int"),
172 ("halstead_N1", "int"),
173 ("halstead_N2", "int"),
174 ("stateTransitions", "string"),
175 ("invariants", "string"),
176 ("tenantBranches", "string"),
177 ("errorMessages", "string"),
178 ("deadCode", "string"),
179 ("methodFingerprints", "string"),
180 ("magicNumbers", "string"),
181 ("callSequences", "string"),
182 ("cyclomaticComplexity", "string"),
183 ("methodTokens", "string"),
184 ("pathConditions", "string"),
185 ];
186 for (id, attr_type) in node_keys {
187 let mut k = BytesStart::new("key");
188 k.push_attribute(("id", *id));
189 k.push_attribute(("for", "node"));
190 k.push_attribute(("attr.name", *id));
191 k.push_attribute(("attr.type", *attr_type));
192 w.write_event(Event::Empty(k))?;
193 w.write_event(Event::Text(BytesText::new("\n")))?;
194 }
195 {
196 let mut k = BytesStart::new("key");
197 k.push_attribute(("id", "type"));
198 k.push_attribute(("for", "edge"));
199 k.push_attribute(("attr.name", "type"));
200 k.push_attribute(("attr.type", "string"));
201 w.write_event(Event::Empty(k))?;
202 w.write_event(Event::Text(BytesText::new("\n")))?;
203 }
204
205 let mut graph_elem = BytesStart::new("graph");
206 graph_elem.push_attribute(("id", "G"));
207 graph_elem.push_attribute(("edgedefault", "directed"));
208 w.write_event(Event::Start(graph_elem))?;
209 w.write_event(Event::Text(BytesText::new("\n")))?;
210
211 for idx in graph.node_indices() {
213 let n = &graph[idx];
214 let mut node_elem = BytesStart::new("node");
215 node_elem.push_attribute(("id", n.id.as_str()));
216 w.write_event(Event::Start(node_elem))?;
217 w.write_event(Event::Text(BytesText::new("\n")))?;
218
219 macro_rules! text_data {
222 ($key:expr, $val:expr) => {{
223 let mut d = BytesStart::new("data");
224 d.push_attribute(("key", $key));
225 w.write_event(Event::Start(d))?;
226 w.write_event(Event::Text(BytesText::new($val)))?;
227 w.write_event(Event::End(BytesEnd::new("data")))?;
228 w.write_event(Event::Text(BytesText::new("\n")))?;
229 }};
230 }
231
232 macro_rules! json_data {
235 ($key:expr, $val:expr) => {{
236 let json = serde_json::to_string($val).unwrap_or_default();
237 let mut d = BytesStart::new("data");
238 d.push_attribute(("key", $key));
239 w.write_event(Event::Start(d))?;
240 w.write_event(Event::CData(BytesCData::new(json.as_str())))?;
241 w.write_event(Event::End(BytesEnd::new("data")))?;
242 w.write_event(Event::Text(BytesText::new("\n")))?;
243 }};
244 }
245
246 text_data!("name", n.name.as_str());
247 text_data!("namespace", n.namespace.as_str());
248 text_data!("lineCount", &n.line_count.to_string());
249 text_data!("methodCount", &n.method_count.to_string());
250
251 if n.halstead_eta1 > 0 || n.halstead_eta2 > 0 || n.halstead_n1 > 0 || n.halstead_n2 > 0 {
252 text_data!("halstead_eta1", &n.halstead_eta1.to_string());
253 text_data!("halstead_eta2", &n.halstead_eta2.to_string());
254 text_data!("halstead_N1", &n.halstead_n1.to_string());
255 text_data!("halstead_N2", &n.halstead_n2.to_string());
256 }
257
258 if let Some(mc) = &n.method_connectivity {
259 json_data!("methodConnectivity", mc);
260 }
261 if let Some(v) = &n.method_fingerprints {
262 json_data!("methodFingerprints", v);
263 }
264 if let Some(v) = &n.call_sequences {
265 json_data!("callSequences", v);
266 }
267 if let Some(v) = &n.cyclomatic_complexity {
268 json_data!("cyclomaticComplexity", v);
269 }
270 if let Some(v) = &n.method_tokens {
271 json_data!("methodTokens", v);
272 }
273 if let Some(v) = &n.path_conditions {
274 json_data!("pathConditions", v);
275 }
276 if let Some(v) = &n.invariants {
277 json_data!("invariants", v);
278 }
279 if let Some(v) = &n.error_messages {
280 json_data!("errorMessages", v);
281 }
282 if let Some(v) = &n.magic_numbers {
283 json_data!("magicNumbers", v);
284 }
285 if let Some(v) = &n.dead_code {
286 json_data!("deadCode", v);
287 }
288 if let Some(v) = &n.tenant_branches {
289 json_data!("tenantBranches", v);
290 }
291 if let Some(v) = &n.state_transitions {
292 json_data!("stateTransitions", v);
293 }
294
295 w.write_event(Event::End(BytesEnd::new("node")))?;
296 w.write_event(Event::Text(BytesText::new("\n")))?;
297 }
298
299 for edge_idx in graph.edge_indices() {
301 let (src_idx, tgt_idx) = graph.edge_endpoints(edge_idx).unwrap();
302 let src_id = &graph[src_idx].id;
303 let tgt_id = &graph[tgt_idx].id;
304 let edge_type = graph.edge_weight(edge_idx).unwrap();
305
306 let mut e = BytesStart::new("edge");
307 e.push_attribute(("source", src_id.as_str()));
308 e.push_attribute(("target", tgt_id.as_str()));
309 w.write_event(Event::Start(e))?;
310 w.write_event(Event::Text(BytesText::new("\n")))?;
311
312 let type_str = match edge_type {
313 EdgeType::MethodCall => "MethodCall",
314 EdgeType::FieldRef => "FieldRef",
315 EdgeType::Inheritance => "Inheritance",
316 };
317 let mut d = BytesStart::new("data");
318 d.push_attribute(("key", "type"));
319 w.write_event(Event::Start(d))?;
320 w.write_event(Event::Text(BytesText::new(type_str)))?;
321 w.write_event(Event::End(BytesEnd::new("data")))?;
322 w.write_event(Event::Text(BytesText::new("\n")))?;
323
324 w.write_event(Event::End(BytesEnd::new("edge")))?;
325 w.write_event(Event::Text(BytesText::new("\n")))?;
326 }
327
328 w.write_event(Event::End(BytesEnd::new("graph")))?;
329 w.write_event(Event::Text(BytesText::new("\n")))?;
330 w.write_event(Event::End(BytesEnd::new("graphml")))?;
331
332 Ok(String::from_utf8(w.into_inner())?)
333}