panproto_protocols/web_document/
odf.rs1use std::collections::HashMap;
6use std::hash::BuildHasher;
7
8use panproto_gat::Theory;
9use panproto_schema::{EdgeRule, Protocol, Schema, SchemaBuilder};
10
11use crate::emit::{children_by_edge, find_roots, vertex_constraints};
12use crate::error::ProtocolError;
13use crate::theories;
14
15#[must_use]
17pub fn protocol() -> Protocol {
18 Protocol {
19 name: "odf".into(),
20 schema_theory: "ThOdfSchema".into(),
21 instance_theory: "ThOdfInstance".into(),
22 edge_rules: edge_rules(),
23 obj_kinds: vec![
24 "document".into(),
25 "body".into(),
26 "paragraph".into(),
27 "span".into(),
28 "table".into(),
29 "row".into(),
30 "cell".into(),
31 "frame".into(),
32 "section".into(),
33 "list".into(),
34 "list-item".into(),
35 "drawing".into(),
36 "style".into(),
37 "master-page".into(),
38 ],
39 constraint_sorts: vec!["style-family".into(), "master-page-name".into()],
40 has_order: true,
41 nominal_identity: true,
42 ..Protocol::default()
43 }
44}
45
46pub fn register_theories<S: BuildHasher>(registry: &mut HashMap<String, Theory, S>) {
48 theories::register_multigraph_wtype_meta(registry, "ThOdfSchema", "ThOdfInstance");
49}
50
51pub fn parse_odf_schema(json: &serde_json::Value) -> Result<Schema, ProtocolError> {
57 let proto = protocol();
58 let mut builder = SchemaBuilder::new(&proto);
59
60 let elements = json
61 .get("elements")
62 .and_then(serde_json::Value::as_object)
63 .ok_or_else(|| ProtocolError::MissingField("elements".into()))?;
64
65 for (name, def) in elements {
66 let kind = def
67 .get("kind")
68 .and_then(serde_json::Value::as_str)
69 .unwrap_or("document");
70 builder = builder.vertex(name, kind, None)?;
71
72 if let Some(v) = def.get("style-family").and_then(serde_json::Value::as_str) {
73 builder = builder.constraint(name, "style-family", v);
74 }
75 if let Some(v) = def
76 .get("master-page-name")
77 .and_then(serde_json::Value::as_str)
78 {
79 builder = builder.constraint(name, "master-page-name", v);
80 }
81
82 if let Some(children) = def.get("children").and_then(serde_json::Value::as_object) {
83 for (child_name, child_def) in children {
84 let child_id = format!("{name}.{child_name}");
85 let child_kind = child_def
86 .get("kind")
87 .and_then(serde_json::Value::as_str)
88 .unwrap_or("paragraph");
89 builder = builder.vertex(&child_id, child_kind, None)?;
90 builder = builder.edge(name, &child_id, "prop", Some(child_name))?;
91 }
92 }
93
94 if let Some(items) = def.get("items").and_then(serde_json::Value::as_array) {
95 for (i, item) in items.iter().enumerate() {
96 if let Some(item_kind) = item.as_str() {
97 let item_id = format!("{name}:item{i}");
98 builder = builder.vertex(&item_id, item_kind, None)?;
99 builder = builder.edge(name, &item_id, "items", Some(item_kind))?;
100 }
101 }
102 }
103 }
104
105 let schema = builder.build()?;
106 Ok(schema)
107}
108
109pub fn emit_odf_schema(schema: &Schema) -> Result<serde_json::Value, ProtocolError> {
115 let structural = &["prop", "items"];
116 let roots = find_roots(schema, structural);
117
118 let mut elements = serde_json::Map::new();
119 for root in &roots {
120 let mut obj = serde_json::Map::new();
121 obj.insert("kind".into(), serde_json::json!(root.kind));
122
123 for c in vertex_constraints(schema, &root.id) {
124 obj.insert(c.sort.to_string(), serde_json::json!(c.value));
125 }
126
127 let props = children_by_edge(schema, &root.id, "prop");
128 if !props.is_empty() {
129 let mut children = serde_json::Map::new();
130 for (edge, child) in &props {
131 let child_name = edge.name.as_deref().unwrap_or(&child.id);
132 let mut child_obj = serde_json::Map::new();
133 child_obj.insert("kind".into(), serde_json::json!(child.kind));
134 children.insert(child_name.to_string(), serde_json::Value::Object(child_obj));
135 }
136 obj.insert("children".into(), serde_json::Value::Object(children));
137 }
138
139 let items = children_by_edge(schema, &root.id, "items");
140 if !items.is_empty() {
141 let arr: Vec<serde_json::Value> = items
142 .iter()
143 .filter_map(|(e, _)| e.name.as_deref().map(|n| serde_json::json!(n)))
144 .collect();
145 obj.insert("items".into(), serde_json::Value::Array(arr));
146 }
147
148 elements.insert(root.id.to_string(), serde_json::Value::Object(obj));
149 }
150
151 Ok(serde_json::json!({ "elements": elements }))
152}
153
154fn edge_rules() -> Vec<EdgeRule> {
155 vec![
156 EdgeRule {
157 edge_kind: "prop".into(),
158 src_kinds: vec![
159 "document".into(),
160 "body".into(),
161 "paragraph".into(),
162 "table".into(),
163 "section".into(),
164 "list".into(),
165 ],
166 tgt_kinds: vec![],
167 },
168 EdgeRule {
169 edge_kind: "items".into(),
170 src_kinds: vec!["document".into(), "body".into(), "list".into()],
171 tgt_kinds: vec![],
172 },
173 ]
174}
175
176#[cfg(test)]
177#[allow(clippy::expect_used, clippy::unwrap_used)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn protocol_def() {
183 let p = protocol();
184 assert_eq!(p.name, "odf");
185 }
186
187 #[test]
188 fn register_theories_works() {
189 let mut registry = HashMap::new();
190 register_theories(&mut registry);
191 assert!(registry.contains_key("ThOdfSchema"));
192 assert!(registry.contains_key("ThOdfInstance"));
193 }
194
195 #[test]
196 fn parse_and_emit() {
197 let json = serde_json::json!({
198 "elements": {
199 "document": {
200 "kind": "document",
201 "children": {
202 "body": {"kind": "body"}
203 },
204 "items": ["paragraph", "table"]
205 },
206 "heading-style": {
207 "kind": "style",
208 "style-family": "paragraph"
209 }
210 }
211 });
212 let schema = parse_odf_schema(&json).expect("should parse");
213 assert!(schema.has_vertex("document"));
214
215 let emitted = emit_odf_schema(&schema).expect("should emit");
216 let s2 = parse_odf_schema(&emitted).expect("re-parse");
217 assert_eq!(schema.vertex_count(), s2.vertex_count());
218 }
219}