busbar_sf_agentscript/graph/render/
graphml.rs1use super::super::{RefGraph, RefNode};
7use petgraph::visit::EdgeRef;
8use std::fmt::Write;
9
10type NodeAttrs<'a> = (
11 &'static str,
12 Option<&'a str>,
13 Option<&'a str>,
14 Option<&'a str>,
15 Option<bool>,
16 (usize, usize),
17);
18
19pub fn render_graphml(graph: &RefGraph) -> String {
26 let inner = graph.inner();
27 let mut output = String::new();
28
29 writeln!(output, r#"<?xml version="1.0" encoding="UTF-8"?>"#).unwrap();
31 writeln!(output, r#"<graphml xmlns="http://graphml.graphdrawing.org/xmlns""#).unwrap();
32 writeln!(output, r#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance""#).unwrap();
33 writeln!(output, r#" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns"#)
34 .unwrap();
35 writeln!(output, r#" http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">"#)
36 .unwrap();
37
38 writeln!(
40 output,
41 r#" <key id="node_type" for="node" attr.name="node_type" attr.type="string"/>"#
42 )
43 .unwrap();
44 writeln!(output, r#" <key id="name" for="node" attr.name="name" attr.type="string"/>"#)
45 .unwrap();
46 writeln!(output, r#" <key id="topic" for="node" attr.name="topic" attr.type="string"/>"#)
47 .unwrap();
48 writeln!(
49 output,
50 r#" <key id="target" for="node" attr.name="target" attr.type="string"/>"#
51 )
52 .unwrap();
53 writeln!(
54 output,
55 r#" <key id="mutable" for="node" attr.name="mutable" attr.type="boolean"/>"#
56 )
57 .unwrap();
58 writeln!(
59 output,
60 r#" <key id="span_start" for="node" attr.name="span_start" attr.type="int"/>"#
61 )
62 .unwrap();
63 writeln!(
64 output,
65 r#" <key id="span_end" for="node" attr.name="span_end" attr.type="int"/>"#
66 )
67 .unwrap();
68 writeln!(output, r#" <key id="label" for="node" attr.name="label" attr.type="string"/>"#)
69 .unwrap();
70
71 writeln!(
73 output,
74 r#" <key id="edge_type" for="edge" attr.name="edge_type" attr.type="string"/>"#
75 )
76 .unwrap();
77
78 writeln!(output, r#" <key id="nodegraphics" for="node" yfiles.type="nodegraphics"/>"#)
80 .unwrap();
81 writeln!(output, r#" <key id="edgegraphics" for="edge" yfiles.type="edgegraphics"/>"#)
82 .unwrap();
83
84 writeln!(output, r#" <graph id="G" edgedefault="directed">"#).unwrap();
86
87 for idx in inner.node_indices() {
89 if let Some(node) = graph.get_node(idx) {
90 let id = idx.index();
91 let (node_type, name, topic, target, mutable, span) = extract_node_attrs(node);
92
93 let label = node.label();
94 let escaped_label = escape_xml(&label);
95
96 writeln!(output, r#" <node id="n{}">"#, id).unwrap();
97 writeln!(output, r#" <data key="node_type">{}</data>"#, node_type).unwrap();
98 writeln!(output, r#" <data key="label">{}</data>"#, escaped_label).unwrap();
99
100 if let Some(n) = name {
101 writeln!(output, r#" <data key="name">{}</data>"#, escape_xml(n)).unwrap();
102 }
103 if let Some(t) = topic {
104 writeln!(output, r#" <data key="topic">{}</data>"#, escape_xml(t)).unwrap();
105 }
106 if let Some(tgt) = target {
107 writeln!(output, r#" <data key="target">{}</data>"#, escape_xml(tgt)).unwrap();
108 }
109 if let Some(m) = mutable {
110 writeln!(output, r#" <data key="mutable">{}</data>"#, m).unwrap();
111 }
112 writeln!(output, r#" <data key="span_start">{}</data>"#, span.0).unwrap();
113 writeln!(output, r#" <data key="span_end">{}</data>"#, span.1).unwrap();
114
115 writeln!(output, r#" </node>"#).unwrap();
116 }
117 }
118
119 for (edge_id, edge) in inner.edge_references().enumerate() {
121 let source = edge.source().index();
122 let target = edge.target().index();
123 let edge_type = edge.weight().label();
124
125 writeln!(
126 output,
127 r#" <edge id="e{}" source="n{}" target="n{}">"#,
128 edge_id, source, target
129 )
130 .unwrap();
131 writeln!(output, r#" <data key="edge_type">{}</data>"#, edge_type).unwrap();
132 writeln!(output, r#" </edge>"#).unwrap();
133 }
134
135 writeln!(output, r#" </graph>"#).unwrap();
137 writeln!(output, r#"</graphml>"#).unwrap();
138
139 output
140}
141
142fn extract_node_attrs(node: &RefNode) -> NodeAttrs<'_> {
144 match node {
145 RefNode::StartAgent { span } => ("start_agent", None, None, None, None, *span),
146 RefNode::Topic { name, span } => ("topic", Some(name.as_str()), None, None, None, *span),
147 RefNode::ActionDef { name, topic, span } => {
148 ("action_def", Some(name.as_str()), Some(topic.as_str()), None, None, *span)
149 }
150 RefNode::ReasoningAction {
151 name,
152 topic,
153 target,
154 span,
155 } => (
156 "reasoning_action",
157 Some(name.as_str()),
158 Some(topic.as_str()),
159 target.as_deref(),
160 None,
161 *span,
162 ),
163 RefNode::Variable {
164 name,
165 mutable,
166 span,
167 } => ("variable", Some(name.as_str()), None, None, Some(*mutable), *span),
168 RefNode::Connection { name, span } => {
169 ("connection", Some(name.as_str()), None, None, None, *span)
170 }
171 }
172}
173
174fn escape_xml(s: &str) -> String {
176 s.replace('&', "&")
177 .replace('<', "<")
178 .replace('>', ">")
179 .replace('"', """)
180 .replace('\'', "'")
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn test_escape_xml() {
189 assert_eq!(escape_xml("hello"), "hello");
190 assert_eq!(escape_xml("<tag>"), "<tag>");
191 assert_eq!(escape_xml("a & b"), "a & b");
192 assert_eq!(escape_xml(r#"say "hello""#), "say "hello"");
193 }
194}