1use std::{
2 fs::File,
3 io::{self, Write},
4 process::Command,
5};
6
7use prefixmap::{IriRef, PrefixMap, PrefixMapError};
8use shex_ast::{
9 Annotation, ObjectValue, Schema, Shape, ShapeExpr, ShapeExprLabel, TripleExpr, ValueSetValue,
10};
11use tracing::debug;
12
13use crate::{
14 find_annotation, object_value2string,
15 shex_to_uml::{ShEx2UmlConfig, ShEx2UmlError, Uml},
16};
17
18use super::{
19 Name, NodeId, UmlCardinality, UmlClass, UmlComponent, UmlEntry, ValueConstraint, PLANTUML,
20};
21use tempfile::TempDir;
22
23pub struct ShEx2Uml {
24 config: ShEx2UmlConfig,
25 current_uml: Uml,
26 current_prefixmap: PrefixMap,
27}
28
29impl ShEx2Uml {
30 pub fn new(config: &ShEx2UmlConfig) -> ShEx2Uml {
31 ShEx2Uml {
32 config: config.clone(),
33 current_uml: Uml::new(),
34 current_prefixmap: PrefixMap::new(),
35 }
36 }
37
38 pub fn as_plantuml<W: Write>(
39 &self,
40 writer: &mut W,
41 mode: &UmlGenerationMode,
42 ) -> Result<(), ShEx2UmlError> {
43 match mode {
44 UmlGenerationMode::AllNodes => {
45 self.current_uml.as_plantuml_all(&self.config, writer)?;
46 Ok(())
47 }
48 UmlGenerationMode::Neighs(str) => {
49 if let Some(node_id) = self.current_uml.get_node(str) {
50 self.current_uml
51 .as_plantuml_neighs(&self.config, writer, &node_id)?;
52 Ok(())
53 } else {
54 Err(ShEx2UmlError::NotFoundLabel { name: str.clone() })
55 }
56 }
57 }
58 }
59
60 pub fn as_image<W: Write>(
62 &self,
63 writer: &mut W,
64 image_format: ImageFormat,
65 mode: &UmlGenerationMode,
66 ) -> Result<(), ShEx2UmlError> {
67 let tempdir = TempDir::new().map_err(|e| ShEx2UmlError::TempFileError { err: e })?;
68 let tempdir_path = tempdir.path();
69 let tempfile_path = tempdir_path.join("temp.uml");
70 let tempfile_name = tempfile_path.display().to_string();
71 let mut tempfile =
72 File::create(tempfile_path).map_err(|e| ShEx2UmlError::CreatingTempUMLFile {
73 tempfile_name: tempfile_name.clone(),
74 error: e,
75 })?;
76 self.as_plantuml(&mut tempfile, mode)?;
77 debug!("ShEx contents stored in temporary file:{}", tempfile_name);
80
81 let (out_param, out_file_name) = match image_format {
82 ImageFormat::PNG => ("-png", tempdir_path.join("temp.png")),
83 ImageFormat::SVG => ("-svg", tempdir_path.join("temp.svg")),
84 };
85 if let Some(plantuml_path) = &self.config.plantuml_path {
86 let mut command = Command::new("java");
87 command
88 .arg("-jar")
89 .arg(plantuml_path)
90 .arg("-o")
91 .arg(tempdir_path.to_string_lossy().to_string())
92 .arg(out_param)
93 .arg(tempfile_name);
94 let command_name = format!("{:?}", &command);
95 debug!("PLANTUML COMMAND:\n{command_name}");
96 let result = command.output();
97 match result {
98 Ok(_) => {
99 let mut temp_file = File::open(out_file_name.as_path()).map_err(|e| {
100 ShEx2UmlError::CantOpenGeneratedTempFile {
101 generated_name: out_file_name.display().to_string(),
102 error: e,
103 }
104 })?;
105 copy(&mut temp_file, writer).map_err(|e| ShEx2UmlError::CopyingTempFile {
106 temp_name: out_file_name.display().to_string(),
107 error: e,
108 })?;
109 Ok(())
110 }
111 Err(e) => Err(ShEx2UmlError::PlantUMLCommandError {
112 command: command_name,
113 error: e,
114 }),
115 }
116 } else {
117 Err(ShEx2UmlError::NoPlantUMLPath {
118 env_name: PLANTUML.to_string(),
119 })
120 }
121 }
122
123 pub fn convert(&mut self, shex: &Schema) -> Result<(), ShEx2UmlError> {
124 self.current_prefixmap = shex.prefixmap().unwrap_or_default();
125 if let Some(shapes) = shex.shapes() {
126 for shape_decl in shapes {
127 let mut name = self.shape_label2name(&shape_decl.id)?;
128 let (node_id, _found) = self.current_uml.get_node_adding_label(&name.name());
129 let component =
130 self.shape_expr2component(&mut name, &shape_decl.shape_expr, &node_id)?;
131 self.current_uml.update_component(node_id, component)?;
132 }
133 }
134 Ok(())
135 }
136
137 fn shape_label2name(&self, label: &ShapeExprLabel) -> Result<Name, ShEx2UmlError> {
138 match label {
139 ShapeExprLabel::IriRef { value } => {
140 iri_ref2name(value, &self.config, &None, &self.current_prefixmap)
141 }
142 ShapeExprLabel::BNode { value: _ } => todo!(),
143 ShapeExprLabel::Start => todo!(),
144 }
145 }
146
147 fn shape_expr2component(
148 &mut self,
149 name: &mut Name,
150 shape_expr: &ShapeExpr,
151 current_node_id: &NodeId,
152 ) -> Result<UmlComponent, ShEx2UmlError> {
153 match shape_expr {
154 ShapeExpr::Shape(shape) => self.shape2component(name, shape, current_node_id),
155 _ => Err(ShEx2UmlError::NotImplemented {
156 msg: "Complex shape expressions are not implemented yet".to_string(),
157 }),
158 }
159 }
160
161 fn shape2component(
162 &mut self,
163 name: &mut Name,
164 shape: &Shape,
165 current_node_id: &NodeId,
166 ) -> Result<UmlComponent, ShEx2UmlError> {
167 if let Some(label) = get_label(&shape.annotations, &self.current_prefixmap, &self.config)? {
168 name.add_label(label.as_str())
169 }
170 let mut uml_class = UmlClass::new(name.clone());
171 if let Some(extends) = &shape.extends {
172 for e in extends.iter() {
173 let extended_name = self.shape_label2name(e)?;
174 let (extended_node, found) = self
175 .current_uml
176 .get_node_adding_label(&extended_name.name());
177 self.current_uml
178 .add_extends(current_node_id, &extended_node);
179 uml_class.add_extends(&extended_node);
180 if !found {
181 self.current_uml.add_component(
182 extended_node,
183 UmlComponent::class(UmlClass::new(extended_name)),
184 )?;
185 }
186 }
187 }
188 if let Some(te) = &shape.expression {
189 match &te.te {
190 TripleExpr::EachOf {
191 id: _,
192 expressions,
193 min: _,
194 max: _,
195 sem_acts: _,
196 annotations: _,
197 } => {
198 for e in expressions {
199 match &e.te {
200 TripleExpr::TripleConstraint {
201 id: _,
202 negated: _,
203 inverse: _,
204 predicate,
205 value_expr,
206 min,
207 max,
208 sem_acts: _,
209 annotations,
210 } => {
211 let pred_name = mk_name(
212 predicate,
213 annotations,
214 &self.config,
215 &self.current_prefixmap,
216 )?;
217 let card = mk_card(min, max)?;
218 let value_constraint = if let Some(se) = value_expr {
219 self.value_expr2value_constraint(
220 se,
221 current_node_id,
222 &pred_name,
223 &card,
224 )?
225 } else {
226 ValueConstraint::default()
227 };
228 match value_constraint {
229 ValueConstraint::None => {}
230 _ => {
231 let entry =
232 UmlEntry::new(pred_name, value_constraint, card);
233 uml_class.add_entry(entry)
234 }
235 }
236 }
237 _ => todo!(),
238 }
239 }
240 }
241 TripleExpr::OneOf {
242 id: _,
243 expressions: _,
244 min: _,
245 max: _,
246 sem_acts: _,
247 annotations: _,
248 } => todo!(),
249 TripleExpr::TripleConstraint {
250 id: _,
251 negated: _,
252 inverse: _,
253 predicate,
254 value_expr,
255 min,
256 max,
257 sem_acts: _,
258 annotations,
259 } => {
260 let pred_name = mk_name(
261 predicate,
262 annotations,
263 &self.config,
264 &self.current_prefixmap,
265 )?;
266 let card = mk_card(min, max)?;
267 let value_constraint = if let Some(se) = value_expr {
268 self.value_expr2value_constraint(se, current_node_id, &pred_name, &card)?
269 } else {
270 ValueConstraint::default()
271 };
272 match value_constraint {
273 ValueConstraint::None => {}
274 _ => {
275 let entry = UmlEntry::new(pred_name, value_constraint, card);
276 uml_class.add_entry(entry)
277 }
278 }
279 }
280 TripleExpr::TripleExprRef(_) => todo!(),
281 }
282 Ok(UmlComponent::class(uml_class))
283 } else {
284 Ok(UmlComponent::class(uml_class))
285 }
286 }
287
288 fn value_expr2value_constraint(
289 &mut self,
290 value_expr: &ShapeExpr,
291 current_node_id: &NodeId,
292 current_predicate: &Name,
293 current_card: &UmlCardinality,
294 ) -> Result<ValueConstraint, ShEx2UmlError> {
295 match value_expr {
296 ShapeExpr::ShapeOr { shape_exprs: _ } => todo!(),
297 ShapeExpr::ShapeAnd { shape_exprs: _ } => todo!(),
298 ShapeExpr::ShapeNot { shape_expr: _ } => todo!(),
299 ShapeExpr::NodeConstraint(nc) => {
300 if let Some(datatype) = nc.datatype() {
301 let name =
302 iri_ref2name(&datatype, &self.config, &None, &self.current_prefixmap)?;
303 Ok(ValueConstraint::datatype(name))
304 } else if let Some(value_set) = nc.values() {
305 let value_set_constraint = value_set2value_constraint(
306 &value_set,
307 &self.config,
308 &self.current_prefixmap,
309 )?;
310 Ok(ValueConstraint::ValueSet(value_set_constraint))
311 } else {
312 todo!()
313 }
314 }
315 ShapeExpr::Shape(_) => todo!(),
316 ShapeExpr::External => todo!(),
317 ShapeExpr::Ref(r) => match &r {
318 ShapeExprLabel::IriRef { value } => {
319 let ref_name =
320 iri_ref2name(value, &self.config, &None, &self.current_prefixmap)?;
321 self.current_uml.add_link(
322 *current_node_id,
323 ref_name,
324 current_predicate.clone(),
325 current_card.clone(),
326 )?;
327 Ok(ValueConstraint::None)
328 }
329 ShapeExprLabel::BNode { value: _ } => todo!(),
330 ShapeExprLabel::Start => todo!(),
331 },
332 }
333 }
334}
335
336fn value_set2value_constraint(
337 value_set: &Vec<ValueSetValue>,
338 config: &ShEx2UmlConfig,
339 prefixmap: &PrefixMap,
340) -> Result<Vec<Name>, ShEx2UmlError> {
341 let mut result = Vec::new();
342 for value in value_set {
343 match value {
344 ValueSetValue::ObjectValue(ObjectValue::IriRef(iri)) => {
345 let name = iri_ref2name(iri, config, &None, prefixmap)?;
346 result.push(name)
347 }
348 ValueSetValue::ObjectValue(ObjectValue::Literal(lit)) => {
349 return Err(ShEx2UmlError::not_implemented(
350 format!("value_set2value_constraint with literal value: {lit:?}").as_str(),
351 ))
352 }
353 _ => {
354 return Err(ShEx2UmlError::not_implemented(
355 format!("value_set2value_constraint with value: {value:?}").as_str(),
356 ))
357 }
358 }
359 }
360 Ok(result)
361}
362
363fn iri_ref2name(
364 iri_ref: &IriRef,
365 _config: &ShEx2UmlConfig,
366 maybe_label: &Option<String>,
367 prefixmap: &PrefixMap,
368) -> Result<Name, ShEx2UmlError> {
369 let mut name = match iri_ref {
370 IriRef::Iri(iri) => Name::new(prefixmap.qualify(iri).as_str(), Some(iri.as_str())),
371 IriRef::Prefixed { prefix, local } => {
372 let iri = prefixmap.resolve_prefix_local(prefix, local)?;
373 Name::new(format!("{prefix}:{local}").as_str(), Some(iri.as_str()))
374 }
375 };
376 if let Some(label) = maybe_label {
377 name.add_label(label)
378 };
379 Ok(name)
380}
381
382fn mk_card(min: &Option<i32>, max: &Option<i32>) -> Result<UmlCardinality, ShEx2UmlError> {
383 let min = if let Some(n) = min { *n } else { 1 };
384 let max = if let Some(n) = max { *n } else { 1 };
385 match (min, max) {
386 (1, 1) => Ok(UmlCardinality::OneOne),
387 (0, -1) => Ok(UmlCardinality::Star),
388 (0, 1) => Ok(UmlCardinality::Optional),
389 (1, -1) => Ok(UmlCardinality::Plus),
390 (m, n) if m >= 0 && n > m => Ok(UmlCardinality::Fixed(m)),
391 (m, n) if m >= 0 && n > m => Ok(UmlCardinality::Range(m, n)),
392 _ => Err(ShEx2UmlError::WrongCardinality { min, max }),
393 }
394}
395
396fn copy<W: Write>(file: &mut File, writer: &mut W) -> Result<(), io::Error> {
397 io::copy(file, writer)?;
398 Ok(())
399}
400
401fn mk_name(
402 iri: &IriRef,
403 annotations: &Option<Vec<Annotation>>,
404 config: &ShEx2UmlConfig,
405 prefixmap: &PrefixMap,
406) -> Result<Name, ShEx2UmlError> {
407 let label = get_label(annotations, prefixmap, config)?;
408 let name = iri_ref2name(iri, config, &label, prefixmap)?;
409 Ok(name)
410}
411
412fn get_label(
413 annotations: &Option<Vec<Annotation>>,
414 prefixmap: &PrefixMap,
415 config: &ShEx2UmlConfig,
416) -> Result<Option<String>, PrefixMapError> {
417 for label in config.annotation_label.iter() {
418 if let Some(value) = find_annotation(annotations, label, prefixmap)? {
419 return Ok(Some(object_value2string(&value)));
420 }
421 }
422 Ok(None)
423}
424
425pub enum ImageFormat {
426 SVG,
427 PNG,
428}
429
430#[derive(Debug, Clone, Default)]
431pub enum UmlGenerationMode {
432 #[default]
434 AllNodes,
435
436 Neighs(String),
438}
439
440impl UmlGenerationMode {
441 pub fn all() -> UmlGenerationMode {
442 UmlGenerationMode::AllNodes
443 }
444
445 pub fn neighs(node: &str) -> UmlGenerationMode {
446 UmlGenerationMode::Neighs(node.to_string())
447 }
448}
449
450#[cfg(test)]
451mod tests {
452 }