Skip to main content

panproto_protocols/web_document/
odf.rs

1//! Open Document Format protocol definition.
2//!
3//! Uses Group E theory: constrained multigraph + W-type + metadata.
4
5use 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/// Returns the ODF protocol definition.
16#[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
46/// Register the component GATs for ODF.
47pub fn register_theories<S: BuildHasher>(registry: &mut HashMap<String, Theory, S>) {
48    theories::register_multigraph_wtype_meta(registry, "ThOdfSchema", "ThOdfInstance");
49}
50
51/// Parse a JSON-based ODF schema into a [`Schema`].
52///
53/// # Errors
54///
55/// Returns [`ProtocolError`] if parsing fails.
56pub 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
109/// Emit a [`Schema`] as a JSON ODF schema.
110///
111/// # Errors
112///
113/// Returns [`ProtocolError`] if emission fails.
114pub 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}