shapes_converter/shex_to_uml/
uml.rs

1use super::Name;
2use super::NodeId;
3use super::ShEx2UmlConfig;
4use super::UmlCardinality;
5use super::UmlComponent;
6use super::UmlEntry;
7use super::UmlError;
8use super::UmlLink;
9use super::ValueConstraint;
10use std::collections::hash_map::*;
11use std::collections::HashMap;
12use std::collections::HashSet;
13use std::hash::Hash;
14use std::io::Write;
15
16#[derive(Debug, PartialEq, Default)]
17pub struct Uml {
18    labels_counter: usize,
19
20    labels: HashMap<String, NodeId>,
21
22    /// Associates a node with an UmlComponent
23    components: HashMap<NodeId, UmlComponent>,
24
25    /// List of links
26    links: Vec<UmlLink>,
27
28    /// Contains a map that keeps track of all the parents of a node
29    extends: HashMap<NodeId, HashSet<NodeId>>,
30
31    /// Outgoing arcs
32    outgoing: HashMap<NodeId, HashSet<NodeId>>,
33
34    /// Incoming arcs
35    incoming: HashMap<NodeId, HashSet<NodeId>>,
36}
37
38impl Uml {
39    pub fn new() -> Uml {
40        Default::default()
41    }
42
43    /// Tries to get a node from a label. If it exists returns the node and true, otherwise, adds the node and returns false
44    pub fn get_node_adding_label(&mut self, label: &str) -> (NodeId, bool) {
45        match self.labels.entry(label.to_string()) {
46            Entry::Occupied(c) => (*c.get(), true),
47            Entry::Vacant(v) => {
48                self.labels_counter += 1;
49                let n = NodeId::new(self.labels_counter);
50                v.insert(n);
51                (n, false)
52            }
53        }
54    }
55
56    /// Search a node from a label. If it does not exist, returno `None``
57    pub fn get_node(&self, label: &str) -> Option<NodeId> {
58        self.labels.get(label).copied()
59    }
60
61    pub fn add_component(&mut self, node: NodeId, component: UmlComponent) -> Result<(), UmlError> {
62        match self.components.entry(node) {
63            Entry::Occupied(_c) => Err(UmlError::NodeIdHasComponent { node_id: node }),
64            Entry::Vacant(v) => {
65                v.insert(component);
66                Ok(())
67            }
68        }
69    }
70
71    pub fn update_component(
72        &mut self,
73        node: NodeId,
74        component: UmlComponent,
75    ) -> Result<(), UmlError> {
76        if let Some(r) = self.components.get_mut(&node) {
77            *r = component
78        } else {
79            self.components.insert(node, component);
80        }
81        Ok(())
82    }
83
84    pub fn children<'a>(
85        &'a self,
86        node: &'a NodeId,
87    ) -> impl Iterator<Item = (&'a NodeId, &'a UmlComponent)> {
88        self.components.iter().filter(|(node_id, _component)| {
89            if let Some(es) = self.extends.get(node_id) {
90                es.contains(node)
91            } else {
92                false
93            }
94        })
95    }
96
97    pub fn add_link(
98        &mut self,
99        source: NodeId,
100        target: Name,
101        link_name: Name,
102        card: UmlCardinality,
103    ) -> Result<(), UmlError> {
104        match self.labels.entry(target.name()) {
105            Entry::Occupied(entry) => {
106                let target = *entry.get();
107                self.make_link(source, target, link_name, card);
108                Ok(())
109            }
110            Entry::Vacant(v) => {
111                self.labels_counter += 1;
112                let target_node_id = NodeId::new(self.labels_counter);
113                v.insert(target_node_id);
114                self.make_link(source, target_node_id, link_name, card);
115                Ok(())
116            }
117        }
118    }
119
120    pub fn make_link(&mut self, source: NodeId, target: NodeId, name: Name, card: UmlCardinality) {
121        let link = UmlLink::new(source, target, name, card);
122        self.links.push(link);
123        insert_map(&mut self.outgoing, source, target);
124        insert_map(&mut self.incoming, target, source);
125    }
126
127    pub fn add_extends(&mut self, source: &NodeId, target: &NodeId) {
128        match self.extends.entry(*source) {
129            Entry::Occupied(mut v) => {
130                v.get_mut().insert(*target);
131            }
132            Entry::Vacant(vacant) => {
133                vacant.insert(HashSet::from([*target]));
134            }
135        }
136    }
137
138    pub fn extends(&self) -> impl Iterator<Item = (&NodeId, &NodeId)> {
139        self.extends
140            .iter()
141            .flat_map(|(n1, vs)| vs.iter().map(move |n2| (n1, n2)))
142    }
143
144    pub fn as_plantuml_all<W: Write>(
145        &self,
146        config: &ShEx2UmlConfig,
147        writer: &mut W,
148    ) -> Result<(), UmlError> {
149        writeln!(writer, "@startuml")?;
150        for (node_id, component) in self.components.iter() {
151            component2plantuml(node_id, component, config, writer)?;
152        }
153        for link in self.links.iter() {
154            link2plantuml(link, config, writer)?;
155        }
156        for (n1, n2) in self.extends() {
157            writeln!(writer, "{n1} -|> {n2}")?;
158        }
159        writeln!(writer, "@enduml")?;
160        Ok(())
161    }
162
163    pub fn as_plantuml_neighs<W: Write>(
164        &self,
165        config: &ShEx2UmlConfig,
166        writer: &mut W,
167        target_node: &NodeId,
168    ) -> Result<(), UmlError> {
169        writeln!(writer, "@startuml")?;
170        let mut serialized_components = HashSet::new();
171
172        // For all components in schema, check if they are neighbours with target_node
173        for (node_id, component) in self.components.iter() {
174            if node_id == target_node
175                || is_in_extends(&self.extends, node_id, target_node)
176                || is_in_extends(&self.extends, target_node, node_id)
177                || is_in_map(&self.outgoing, target_node, node_id)
178                || is_in_map(&self.incoming, target_node, node_id)
179                    && !serialized_components.contains(node_id)
180            {
181                serialized_components.insert(node_id);
182                component2plantuml(node_id, component, config, writer)?;
183            }
184        }
185        for link in self.links.iter() {
186            if link.source == *target_node || link.target == *target_node {
187                link2plantuml(link, config, writer)?;
188            }
189        }
190        for (n1, n2) in self.extends() {
191            if n1 == target_node || n2 == target_node {
192                writeln!(writer, "{n1} -|> {n2}")?;
193            }
194        }
195        writeln!(writer, "@enduml")?;
196        Ok(())
197    }
198}
199
200fn component2plantuml<W: Write>(
201    node_id: &NodeId,
202    component: &UmlComponent,
203    config: &ShEx2UmlConfig,
204    writer: &mut W,
205) -> Result<(), UmlError> {
206    match component {
207        UmlComponent::UmlClass(class) => {
208            let name = if config.replace_iri_by_label() {
209                if let Some(label) = class.label() {
210                    label
211                } else {
212                    class.name()
213                }
214            } else {
215                class.name()
216            };
217            let href = if let Some(href) = class.href() {
218                format!("[[{href} {name}]]")
219            } else {
220                "".to_string()
221            };
222            writeln!(
223                writer,
224                "class \"{}\" as {} <<(S,#FF7700)>> {} {{ ",
225                name, node_id, href
226            )?;
227            for entry in class.entries() {
228                entry2plantuml(entry, config, writer)?;
229            }
230            writeln!(writer, "}}")?;
231        }
232    }
233    Ok(())
234}
235
236fn link2plantuml<W: Write>(
237    link: &UmlLink,
238    config: &ShEx2UmlConfig,
239    writer: &mut W,
240) -> Result<(), UmlError> {
241    let source = format!("{}", link.source);
242    let card = card2plantuml(&link.card);
243    let target = format!("{}", link.target);
244    let name = name2plantuml(&link.name, config);
245    writeln!(writer, "{source} --> \"{card}\" {target} : {name}")?;
246    Ok(())
247}
248
249fn entry2plantuml<W: Write>(
250    entry: &UmlEntry,
251    config: &ShEx2UmlConfig,
252    writer: &mut W,
253) -> Result<(), UmlError> {
254    let property = name2plantuml(&entry.name, config);
255    let value_constraint = value_constraint2plantuml(&entry.value_constraint, config);
256    let card = card2plantuml(&entry.card);
257    writeln!(writer, "{} : {} {}", property, value_constraint, card)?;
258    writeln!(writer, "--")?;
259    Ok(())
260}
261
262fn name2plantuml(name: &Name, config: &ShEx2UmlConfig) -> String {
263    let str = if config.replace_iri_by_label() {
264        if let Some(label) = name.label() {
265            label
266        } else {
267            name.name()
268        }
269    } else {
270        name.name()
271    };
272    if let Some(href) = name.href() {
273        format!("[[{href} {}]]", str)
274    } else {
275        name.name()
276    }
277}
278
279fn value_constraint2plantuml(vc: &ValueConstraint, config: &ShEx2UmlConfig) -> String {
280    match vc {
281        ValueConstraint::Any => ".".to_string(),
282        ValueConstraint::Datatype(dt) => name2plantuml(dt, config),
283        ValueConstraint::Ref(r) => format!("@{}", name2plantuml(r, config)),
284        ValueConstraint::None => "".to_string(),
285        ValueConstraint::ValueSet(values) => {
286            let mut str = String::new();
287            str.push_str("[ ");
288            for value in values {
289                let name_puml = name2plantuml(value, config);
290                str.push_str(name_puml.as_str());
291                str.push_str(", ");
292            }
293            str.push_str(" ]");
294            str.to_string()
295        }
296    }
297}
298
299fn card2plantuml(card: &UmlCardinality) -> String {
300    match card {
301        UmlCardinality::OneOne => " ".to_string(),
302        UmlCardinality::Star => "*".to_string(),
303        UmlCardinality::Plus => "+".to_string(),
304        UmlCardinality::Optional => "?".to_string(),
305        UmlCardinality::Range(m, n) => format!("{m}-{n}"),
306        UmlCardinality::Fixed(m) => format!("{{{m}}}"),
307    }
308}
309
310fn is_in_extends(
311    extends: &HashMap<NodeId, HashSet<NodeId>>,
312    node: &NodeId,
313    target: &NodeId,
314) -> bool {
315    if let Some(es) = extends.get(node) {
316        es.contains(target)
317    } else {
318        false
319    }
320}
321
322fn insert_map<A, B>(map: &mut HashMap<A, HashSet<B>>, source: A, target: B)
323where
324    A: Eq + Hash,
325    B: Eq + Hash,
326{
327    match map.entry(source) {
328        Entry::Occupied(mut entry) => {
329            let set = entry.get_mut();
330            set.insert(target);
331        }
332        Entry::Vacant(v) => {
333            v.insert(HashSet::from([target]));
334        }
335    }
336}
337
338fn is_in_map<A, B>(map: &HashMap<A, HashSet<B>>, source: &A, target: &B) -> bool
339where
340    A: Eq + Hash,
341    B: Eq + Hash,
342{
343    if let Some(es) = map.get(source) {
344        es.contains(target)
345    } else {
346        false
347    }
348}