From f9b2099a146edc3943bdf89a12c83be7af1a6ad0 Mon Sep 17 00:00:00 2001 From: Jose Labra Date: Tue, 23 Sep 2025 09:11:08 +0200 Subject: [PATCH 1/6] Improved service description and changed python.yml to see if it publishes windows wheels --- .github/workflows/python.yml | 2 +- CHANGELOG.md | 14 +++++++++ sparql_service/src/entailment_profile.rs | 2 +- sparql_service/src/entailment_regime.rs | 2 +- sparql_service/src/graph_collection.rs | 16 ++++++++-- sparql_service/src/named_graph_description.rs | 2 +- .../src/service_description_parser.rs | 29 +++++++++---------- srdf/src/srdf_parser/rdf_node_parser.rs | 8 ++--- 8 files changed, 47 insertions(+), 28 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 166eb358..8bc87e67 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -173,7 +173,7 @@ jobs: if: github.event.action == 'published' runs-on: ubuntu-latest # needs: [linux, windows, macos, sdist] - needs: [linux, macos] + needs: [linux, macos, windows] steps: - uses: actions/download-artifact@v4 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index d870f357..c37e7c70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,24 @@ This ChangeLog follows the Keep a ChangeLog guidelines](https://keepachangelog.c ### Changed ### Removed +## 0.1.103 +### Added + +### Fixed +- GraphCollection in service description contains a collection of named graphs (before was a collection of graph descriptions) +- The parser now parses also the available graphs + +### Changed + + +### Removed + ## 0.1.102 ### Added - Comparison between schemas - Added documentation about comparison between schemas +- Published Windows amd-64 Python wheel +- Added parsed title in SPARQL service description from property dcterms:title ### Fixed - Cleaned and Clippied the code that we did in a hurry during Biohackathon diff --git a/sparql_service/src/entailment_profile.rs b/sparql_service/src/entailment_profile.rs index 34733ae5..d6a3eeff 100644 --- a/sparql_service/src/entailment_profile.rs +++ b/sparql_service/src/entailment_profile.rs @@ -2,7 +2,7 @@ use iri_s::IriS; use serde::{Deserialize, Serialize}; use std::fmt::Display; -#[derive(Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize)] +#[derive(Clone, PartialEq, Eq, Hash, Debug, Default, Serialize, Deserialize)] pub enum EntailmentProfile { #[default] DL, diff --git a/sparql_service/src/entailment_regime.rs b/sparql_service/src/entailment_regime.rs index bf4152ee..5c5cac69 100644 --- a/sparql_service/src/entailment_regime.rs +++ b/sparql_service/src/entailment_regime.rs @@ -2,7 +2,7 @@ use iri_s::IriS; use serde::{Deserialize, Serialize}; use std::fmt::Display; -#[derive(Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize)] +#[derive(Clone, PartialEq, Eq, Hash, Debug, Default, Serialize, Deserialize)] pub enum EntailmentRegime { #[default] Simple, diff --git a/sparql_service/src/graph_collection.rs b/sparql_service/src/graph_collection.rs index 81964d6a..a847e6fb 100644 --- a/sparql_service/src/graph_collection.rs +++ b/sparql_service/src/graph_collection.rs @@ -1,4 +1,4 @@ -use crate::GraphDescription; +use crate::NamedGraphDescription; use serde::{Deserialize, Serialize}; use srdf::IriOrBlankNode; use std::{collections::HashSet, fmt::Display, hash::Hash}; @@ -6,8 +6,9 @@ use std::{collections::HashSet, fmt::Display, hash::Hash}; #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] pub struct GraphCollection { id: IriOrBlankNode, + #[serde(skip_serializing_if = "HashSet::is_empty")] - collection: HashSet, + collection: HashSet, } impl GraphCollection { @@ -17,6 +18,11 @@ impl GraphCollection { collection: HashSet::new(), } } + + pub fn with_collection>(mut self, graphs: I) -> Self { + self.collection = HashSet::from_iter(graphs); + self + } } impl Hash for GraphCollection { @@ -27,6 +33,10 @@ impl Hash for GraphCollection { impl Display for GraphCollection { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Id: {}", self.id) + write!(f, "Id: {}", self.id)?; + for graph in &self.collection { + writeln!(f, "\nGraph: {}", graph)?; + } + Ok(()) } } diff --git a/sparql_service/src/named_graph_description.rs b/sparql_service/src/named_graph_description.rs index 7fbee835..c5cac8b5 100644 --- a/sparql_service/src/named_graph_description.rs +++ b/sparql_service/src/named_graph_description.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use srdf::IriOrBlankNode; use std::fmt::Display; -#[derive(Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize)] +#[derive(Clone, PartialEq, Eq, Hash, Debug, Default, Serialize, Deserialize)] pub struct NamedGraphDescription { #[serde(skip_serializing_if = "Option::is_none")] id: Option, diff --git a/sparql_service/src/service_description_parser.rs b/sparql_service/src/service_description_parser.rs index 4ae059fc..56154d17 100644 --- a/sparql_service/src/service_description_parser.rs +++ b/sparql_service/src/service_description_parser.rs @@ -10,13 +10,13 @@ use crate::{ }; use iri_s::IriS; use srdf::{ - FocusRDF, IriOrBlankNode, Object, PResult, RDFNodeParse, RDFParser, get_focus_iri_or_bnode, - numeric_literal::NumericLiteral, object, ok, optional, parse_property_values, property_iri, + FocusRDF, IriOrBlankNode, PResult, RDFNodeParse, RDFParser, get_focus_iri_or_bnode, + numeric_literal::NumericLiteral, ok, optional, parse_property_values, property_iri, property_iri_or_bnode, property_number, property_string, property_values_iri, set_focus_iri_or_bnode, }; use std::{collections::HashSet, fmt::Debug}; -use tracing::{debug, trace}; +use tracing::trace; type Result = std::result::Result; @@ -229,7 +229,7 @@ pub fn available_graphs( node: &IriOrBlankNode, ) -> impl RDFNodeParse> where - RDF: FocusRDF, + RDF: FocusRDF + 'static, { set_focus_iri_or_bnode(node).with(parse_property_values( &SD_AVAILABLE_GRAPHS, @@ -239,14 +239,13 @@ where pub fn available_graph() -> impl RDFNodeParse where - RDF: FocusRDF, + RDF: FocusRDF + 'static, { - object().then( - |node| match >::try_into(node) { - Ok(ib) => ok(&GraphCollection::new(&ib)), - Err(_) => todo!(), - }, - ) + get_focus_iri_or_bnode().then(|focus| { + parse_property_values(&SD_NAMED_GRAPH, named_graph()).map(move |named_graphs| { + GraphCollection::new(&focus.clone()).with_collection(named_graphs.into_iter()) + }) + }) } pub fn default_dataset(node: &IriOrBlankNode) -> impl RDFNodeParse @@ -304,7 +303,7 @@ where .with_classes(classes) .with_class_partition(class_partition) .with_property_partition(property_partition); - debug!("parsed graph_description: {d}"); + trace!("parsed graph_description: {d}"); d }, ), @@ -340,7 +339,7 @@ where .and(name()) .and(parse_property_values(&SD_GRAPH, graph())) .map(|((focus, name), graphs)| { - debug!( + trace!( "named_graph_description: focus={focus}, name={name}, graphs={}", graphs.len() ); @@ -412,9 +411,9 @@ pub fn class_partition() -> impl RDFNodeParse where RDF: FocusRDF + 'static, { - debug!("parsing class_partition"); + trace!("parsing class_partition"); get_focus_iri_or_bnode().then(move |focus| { - debug!("parsing class_partition with focus={focus}"); + trace!("parsing class_partition with focus={focus}"); ok(&focus) .and(property_iri(&VOID_CLASS)) .and(parse_property_values(&VOID_PROPERTY, property_partition())) diff --git a/srdf/src/srdf_parser/rdf_node_parser.rs b/srdf/src/srdf_parser/rdf_node_parser.rs index 1366f0a5..48d65907 100644 --- a/srdf/src/srdf_parser/rdf_node_parser.rs +++ b/srdf/src/srdf_parser/rdf_node_parser.rs @@ -1394,16 +1394,14 @@ where { // debug!("property_number: property={}", property); property_value(property).flat_map(|term| { - debug!("property_number: term={}", term); let lit = term_to_number::(&term); if lit.is_err() { - debug!( + trace!( "property_number: term is not a number: {}, err: {}", term, lit.as_ref().err().unwrap() ); } - debug!("Number literal: {:?}", lit); lit }) } @@ -1469,7 +1467,7 @@ where term: format!("{term}"), } })?; - debug!("converted to literal: {:?}", literal); + trace!("converted to literal: {:?}", literal); let slit: SLiteral = literal .try_into() .map_err(|_e| RDFParseError::ExpectedSLiteral { @@ -1704,7 +1702,6 @@ pub fn get_focus_iri_or_bnode() -> impl RDFNodeParse(&term).map_err(|e| { trace!("Error converting term to IRI or BlankNode: {}", e); @@ -1713,7 +1710,6 @@ where error: e.to_string(), } }); - debug!("Focus node as IRI or BlankNode: {:?}", node); node }) } From 9effced2398a8372a55a95df9c3589e799511746 Mon Sep 17 00:00:00 2001 From: Jose Labra Date: Tue, 23 Sep 2025 09:11:21 +0200 Subject: [PATCH 2/6] Release 0.1.103 sparql_service@0.1.103 srdf@0.1.103 Generated by cargo-workspaces --- sparql_service/Cargo.toml | 2 +- srdf/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sparql_service/Cargo.toml b/sparql_service/Cargo.toml index 1e77a154..9ec014f0 100755 --- a/sparql_service/Cargo.toml +++ b/sparql_service/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sparql_service" -version = "0.1.102" +version = "0.1.103" authors.workspace = true description.workspace = true edition.workspace = true diff --git a/srdf/Cargo.toml b/srdf/Cargo.toml index 7cd3f955..975ed03a 100644 --- a/srdf/Cargo.toml +++ b/srdf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "srdf" -version = "0.1.102" +version = "0.1.103" authors.workspace = true description.workspace = true documentation = "https://docs.rs/srdf" From 1c6d63cee1e7fa49f8f0aec4e19f1b383528092e Mon Sep 17 00:00:00 2001 From: Jose Labra Date: Tue, 23 Sep 2025 19:37:47 +0200 Subject: [PATCH 3/6] Improved error message when prefix not found according to issue #331 --- examples/simple.shex | 2 +- mie/Cargo.toml | 1 + mie/src/mie.rs | 58 ++++++++++++++++--- prefixmap/src/deref.rs | 8 ++- prefixmap/src/iri_ref.rs | 8 ++- prefixmap/src/prefixmap.rs | 6 +- prefixmap/src/prefixmap_error.rs | 2 +- rudof_cli/src/cli.rs | 2 +- shapes_converter/src/shex_to_uml/shex2uml.rs | 15 ++++- shapes_converter/src/shex_to_uml/uml.rs | 3 + .../src/shex_to_uml/uml_component.rs | 9 +++ shex_compact/src/shex_grammar.rs | 55 ------------------ shex_compact/src/shex_parser.rs | 6 -- sparql_service/src/graph_collection.rs | 4 ++ sparql_service/src/named_graph_description.rs | 4 ++ sparql_service/src/service_description.rs | 36 ++++++++++-- .../src/service_description_parser.rs | 2 +- 17 files changed, 136 insertions(+), 85 deletions(-) diff --git a/examples/simple.shex b/examples/simple.shex index 575dbc77..38e6b273 100644 --- a/examples/simple.shex +++ b/examples/simple.shex @@ -1,7 +1,7 @@ prefix : prefix xsd: -:Person { :name xsd:string ; +:Person { pp:name xsd:string ; :birthdate xsd:date ? ; :enrolledIn @:Course * } diff --git a/mie/Cargo.toml b/mie/Cargo.toml index f605da68..cc575045 100755 --- a/mie/Cargo.toml +++ b/mie/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true [dependencies] thiserror.workspace = true +iri_s.workspace = true serde.workspace = true serde_json.workspace = true tracing = { workspace = true } diff --git a/mie/src/mie.rs b/mie/src/mie.rs index 8838750f..ddd63f8b 100644 --- a/mie/src/mie.rs +++ b/mie/src/mie.rs @@ -1,4 +1,5 @@ use hashlink::LinkedHashMap; +use iri_s::IriS; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::Display; @@ -15,7 +16,7 @@ pub struct Mie { schema_info: SchemaInfo, /// Prefixes defined in the endpoint - prefixes: HashMap, + prefixes: HashMap, /// Shape expressions defined in the schema shape_expressions: HashMap, @@ -30,16 +31,28 @@ pub struct Mie { cross_references: HashMap, /// Statistics about the data + #[serde(skip_serializing_if = "HashMap::is_empty")] data_statistics: HashMap, } /// Statistics about the data #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] pub struct DataStatistics { - classes: isize, - properties: isize, + /// Number of classes + #[serde(skip_serializing_if = "Option::is_none")] + classes: Option, + + /// Number of properties + #[serde(skip_serializing_if = "Option::is_none")] + properties: Option, + + #[serde(skip_serializing_if = "HashMap::is_empty")] class_partitions: HashMap, + + #[serde(skip_serializing_if = "HashMap::is_empty")] property_partitions: HashMap, + + #[serde(skip_serializing_if = "HashMap::is_empty")] cross_references: HashMap>, } @@ -47,33 +60,48 @@ pub struct DataStatistics { #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] pub struct SchemaInfo { /// Title of the schema + #[serde(skip_serializing_if = "Option::is_none")] title: Option, /// Description of the schema + #[serde(skip_serializing_if = "Option::is_none")] description: Option, /// SPARQL endpoint URL + #[serde(skip_serializing_if = "Option::is_none")] endpoint: Option, /// Base URI for the schema + #[serde(skip_serializing_if = "Option::is_none")] base_uri: Option, /// Named graphs used in the endpoint - graphs: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + graphs: Vec, } /// Shape expressions defined in the schema #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] pub struct ShapeExpression { + /// Description of the Shape Expression + #[serde(skip_serializing_if = "Option::is_none")] description: Option, + + /// Shape expressions content + #[serde(skip_serializing_if = "String::is_empty")] shape_expr: String, } /// RDF examples #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] pub struct RdfExample { + #[serde(skip_serializing_if = "Option::is_none")] description: Option, + + #[serde(skip_serializing_if = "String::is_empty")] rdf: String, + + #[serde(skip_serializing_if = "HashMap::is_empty")] other_fields: HashMap, } @@ -97,7 +125,7 @@ pub struct CrossReference { impl Mie { pub fn new( schema_info: SchemaInfo, - prefixes: HashMap, + prefixes: HashMap, shape_expressions: HashMap, sample_rdf_entries: HashMap, sparql_query_examples: HashMap, @@ -123,6 +151,14 @@ impl Mie { self.schema_info.title = Some(title.to_string()); } + pub fn add_graphs>(&mut self, iter: I) { + self.schema_info.graphs = iter.collect() + } + + pub fn add_prefixes(&mut self, prefixes: HashMap) { + self.prefixes = prefixes; + } + pub fn to_yaml(&self) -> Yaml { let mut result = LinkedHashMap::new(); result.insert( @@ -132,7 +168,10 @@ impl Mie { if !self.prefixes.is_empty() { let mut prefixes_yaml = LinkedHashMap::new(); for (k, v) in &self.prefixes { - prefixes_yaml.insert(Yaml::String(k.clone()), Yaml::String(v.clone())); + prefixes_yaml.insert( + Yaml::String(k.clone()), + Yaml::String(v.as_str().to_string()), + ); } result.insert( Yaml::String("prefixes".to_string()), @@ -301,16 +340,17 @@ impl Display for Mie { #[cfg(test)] mod tests { + use iri_s::iri; use yaml_rust2::YamlEmitter; use super::*; #[test] fn test_mie_creation() { let mut prefixes = HashMap::new(); - prefixes.insert("ex".to_string(), "http://example.org/".to_string()); + prefixes.insert("ex".to_string(), iri!("http://example.org/")); prefixes.insert( "rdf".to_string(), - "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), + iri!("http://www.w3.org/1999/02/22-rdf-syntax-ns#"), ); let mut shape_expressions = HashMap::new(); @@ -331,7 +371,7 @@ mod tests { description: Some("An example schema for testing".to_string()), endpoint: Some("http://example.org/sparql".to_string()), base_uri: Some("http://example.org/".to_string()), - graphs: vec!["http://example.org/graph1".to_string()], + graphs: vec![iri!("http://example.org/graph1")], }, prefixes, shape_expressions, diff --git a/prefixmap/src/deref.rs b/prefixmap/src/deref.rs index 4dde9d9a..60d04370 100644 --- a/prefixmap/src/deref.rs +++ b/prefixmap/src/deref.rs @@ -9,8 +9,12 @@ pub enum DerefError { #[error(transparent)] IriSError(#[from] IriSError), - #[error(transparent)] - PrefixMapError(#[from] PrefixMapError), + #[error("Error obtaining IRI for '{alias}:{local}': {error}")] + DerefPrefixMapError { + alias: String, + local: String, + error: PrefixMapError, + }, #[error("No prefix map to dereference prefixed name {prefix}{local}")] NoPrefixMapPrefixedName { prefix: String, local: String }, diff --git a/prefixmap/src/iri_ref.rs b/prefixmap/src/iri_ref.rs index 256e8054..c6d92287 100644 --- a/prefixmap/src/iri_ref.rs +++ b/prefixmap/src/iri_ref.rs @@ -62,7 +62,13 @@ impl Deref for IriRef { local: local.clone(), }), Some(prefixmap) => { - let iri = prefixmap.resolve_prefix_local(prefix, local)?; + let iri = prefixmap.resolve_prefix_local(prefix, local).map_err(|e| { + DerefError::DerefPrefixMapError { + alias: prefix.to_string(), + local: local.to_string(), + error: e, + } + })?; Ok(IriRef::Iri(iri)) } }, diff --git a/prefixmap/src/prefixmap.rs b/prefixmap/src/prefixmap.rs index 210fd200..805cf6eb 100644 --- a/prefixmap/src/prefixmap.rs +++ b/prefixmap/src/prefixmap.rs @@ -9,7 +9,7 @@ use std::str::FromStr; use std::{collections::HashMap, fmt}; /// Contains declarations of prefix maps which are used in TURTLE, SPARQL and ShEx -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Default)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Default)] #[serde(transparent)] pub struct PrefixMap { /// Proper prefix map associations of an alias `String` to an `IriS` @@ -481,6 +481,10 @@ impl PrefixMap { } Ok(()) } + + pub fn aliases(&self) -> impl Iterator { + self.map.keys() + } } impl fmt::Display for PrefixMap { diff --git a/prefixmap/src/prefixmap_error.rs b/prefixmap/src/prefixmap_error.rs index 915510ed..e57c3b54 100644 --- a/prefixmap/src/prefixmap_error.rs +++ b/prefixmap/src/prefixmap_error.rs @@ -8,7 +8,7 @@ pub enum PrefixMapError { #[error(transparent)] IriSError(#[from] IriSError), - #[error("Prefix '{prefix}' not found in PrefixMap '{prefixmap}'")] + #[error("Alias '{prefix}' not found in prefix map\nAvailable aliases: [{}]", prefixmap.aliases().cloned().collect::>().join(", "))] PrefixNotFound { prefix: String, prefixmap: PrefixMap, diff --git a/rudof_cli/src/cli.rs b/rudof_cli/src/cli.rs index 490122a8..09d04268 100644 --- a/rudof_cli/src/cli.rs +++ b/rudof_cli/src/cli.rs @@ -421,7 +421,7 @@ pub enum Command { long = "result-format", value_name = "FORMAT", help = "Ouput result format", - default_value_t = ResultShExValidationFormat::Turtle + default_value_t = ResultShExValidationFormat::Compact )] result_format: ResultShExValidationFormat, diff --git a/shapes_converter/src/shex_to_uml/shex2uml.rs b/shapes_converter/src/shex_to_uml/shex2uml.rs index eaa5d615..40b27e10 100644 --- a/shapes_converter/src/shex_to_uml/shex2uml.rs +++ b/shapes_converter/src/shex_to_uml/shex2uml.rs @@ -89,8 +89,19 @@ impl ShEx2Uml { ) -> Result { match shape_expr { ShapeExpr::Shape(shape) => self.shape2component(name, shape, current_node_id), - _ => Err(ShEx2UmlError::NotImplemented { - msg: "Complex shape expressions are not implemented yet".to_string(), + ShapeExpr::ShapeOr { shape_exprs } => { + let cs: Vec<_> = shape_exprs + .iter() + .map(|se| { + let c = self.shape_expr2component(name, &se.se, current_node_id)?; + Ok::(c) + }) + .flatten() + .collect(); + Ok(UmlComponent::or(cs.into_iter())) + } + other => Err(ShEx2UmlError::NotImplemented { + msg: format!("Complex shape expressions are not implemented yet\nShape: {other:?}"), }), } } diff --git a/shapes_converter/src/shex_to_uml/uml.rs b/shapes_converter/src/shex_to_uml/uml.rs index e0fc8e14..ccad8d1c 100644 --- a/shapes_converter/src/shex_to_uml/uml.rs +++ b/shapes_converter/src/shex_to_uml/uml.rs @@ -228,6 +228,9 @@ fn component2plantuml( } writeln!(writer, "}}")?; } + UmlComponent::Or { exprs: _ } => todo!(), + UmlComponent::Not { expr: _ } => todo!(), + UmlComponent::And { exprs: _ } => todo!(), } Ok(()) } diff --git a/shapes_converter/src/shex_to_uml/uml_component.rs b/shapes_converter/src/shex_to_uml/uml_component.rs index 0ef0ebfa..1f0673b7 100644 --- a/shapes_converter/src/shex_to_uml/uml_component.rs +++ b/shapes_converter/src/shex_to_uml/uml_component.rs @@ -3,10 +3,19 @@ use super::UmlClass; #[derive(Debug, PartialEq)] pub enum UmlComponent { UmlClass(UmlClass), + Or { exprs: Vec }, + Not { expr: Box }, + And { exprs: Vec }, } impl UmlComponent { pub fn class(class: UmlClass) -> UmlComponent { UmlComponent::UmlClass(class) } + + pub fn or>(cs: I) -> UmlComponent { + UmlComponent::Or { + exprs: cs.collect(), + } + } } diff --git a/shex_compact/src/shex_grammar.rs b/shex_compact/src/shex_grammar.rs index fdc42afc..e2cd48d8 100644 --- a/shex_compact/src/shex_grammar.rs +++ b/shex_compact/src/shex_grammar.rs @@ -47,61 +47,6 @@ pub(crate) fn shex_statement<'a>() -> impl FnMut(Span<'a>) -> IRes<'a, ShExState ) } -/* -fn empty(i: Span) -> IRes { - let (i, _) = tws0(i)?; - Ok((i, ShExStatement::Empty)) -} -*/ - -/*pub(crate) fn shex_statement<'a>() -> impl FnMut(Span<'a>) -> IRes<'a, Vec> { - traced("shex_statement", move |i| { - let (i, (ds, _, maybe_sts)) = tuple((directives, tws0, opt(rest_shex_statements)))(i)?; - let mut result = Vec::new(); - result.extend(ds); - match maybe_sts { - None => {} - Some(sts) => { - result.extend(sts); - } - } - Ok((i, result)) - }) -} - -/// From [1] rest_shex_statements = ((notStartAction | startActions) statement*) -fn rest_shex_statements(i: Span) -> IRes> { - let (i, (s, _, ss, _)) = tuple(( - alt((not_start_action, start_actions)), - tws0, - statements, - tws0, - ))(i)?; - let mut rs = vec![s]; - rs.extend(ss); - Ok((i, rs)) -} - -fn directives(i: Span) -> IRes> { - let (i, vs) = many1( - //tuple(( - directive - // , - // tws0 - //)) - )(i)?; - // let mut rs = Vec::new(); - /*for v in vs { - let (d, _) = v; - rs.push(d); - }*/ - Ok((i, vs)) -} - -fn statements(i: Span) -> IRes> { - many0(statement)(i) -} */ - /// `[2] directive ::= baseDecl | prefixDecl | importDecl` fn directive(i: Span) -> IRes { alt((base_decl(), prefix_decl(), import_decl()))(i) diff --git a/shex_compact/src/shex_parser.rs b/shex_compact/src/shex_parser.rs index da7ed086..b242091d 100644 --- a/shex_compact/src/shex_parser.rs +++ b/shex_compact/src/shex_parser.rs @@ -148,12 +148,6 @@ impl<'a> Iterator for StatementIterator<'a> { self.done = true; } } - - /*if r.is_none() && !self.src.is_empty() { - r = Some(Err(ParseError::Custom { - msg: format!("trailing bytes {}", self.src), - })); - }*/ r } } diff --git a/sparql_service/src/graph_collection.rs b/sparql_service/src/graph_collection.rs index a847e6fb..4c7b50ed 100644 --- a/sparql_service/src/graph_collection.rs +++ b/sparql_service/src/graph_collection.rs @@ -23,6 +23,10 @@ impl GraphCollection { self.collection = HashSet::from_iter(graphs); self } + + pub fn named_graph_descriptions(&self) -> impl Iterator { + self.collection.iter() + } } impl Hash for GraphCollection { diff --git a/sparql_service/src/named_graph_description.rs b/sparql_service/src/named_graph_description.rs index c5cac8b5..f34c9d73 100644 --- a/sparql_service/src/named_graph_description.rs +++ b/sparql_service/src/named_graph_description.rs @@ -36,6 +36,10 @@ impl NamedGraphDescription { pub fn id(&self) -> &Option { &self.id } + + pub fn name(&self) -> &IriS { + &self.name + } } impl Display for NamedGraphDescription { diff --git a/sparql_service/src/service_description.rs b/sparql_service/src/service_description.rs index 5b7f657c..b50effbe 100644 --- a/sparql_service/src/service_description.rs +++ b/sparql_service/src/service_description.rs @@ -7,10 +7,11 @@ use crate::{ use iri_s::IriS; use itertools::Itertools; use mie::Mie; +use prefixmap::PrefixMap; use serde::{Deserialize, Serialize}; use srdf::{RDFFormat, ReaderMode, SRDFGraph}; use std::{ - collections::HashSet, + collections::{HashMap, HashSet}, fmt::Display, io::{self}, path::Path, @@ -45,6 +46,9 @@ pub struct ServiceDescription { #[serde(skip_serializing_if = "Vec::is_empty")] available_graphs: Vec, + + #[serde(skip_serializing_if = "Option::is_none")] + prefixmap: Option, } impl ServiceDescription { @@ -57,6 +61,7 @@ impl ServiceDescription { feature: HashSet::new(), result_format: HashSet::new(), available_graphs: Vec::new(), + prefixmap: None, } } @@ -65,6 +70,11 @@ impl ServiceDescription { self } + pub fn with_prefixmap(mut self, prefixmap: Option) -> Self { + self.prefixmap = prefixmap; + self + } + pub fn add_title(&mut self, title: Option<&str>) { self.title = title.map(|t| t.to_string()); } @@ -138,9 +148,17 @@ impl ServiceDescription { mie.add_title(title); } - for _graph in self.available_graphs.iter() { - // let graph_name = graph.graph_name().as_ref().map(|g| g.as_str()); - // mie.add_graph(graphs.service2mie()); + let mut graph_names = Vec::new(); + for graph_collection in self.available_graphs.iter() { + for named_graph_descr in graph_collection.named_graph_descriptions() { + let name = named_graph_descr.name(); + graph_names.push(name.clone()); + } + mie.add_graphs(graph_names.clone().into_iter()); + } + + if let Some(prefixmap) = &self.prefixmap { + mie.add_prefixes(cnv_prefixmap(prefixmap)) } mie } @@ -154,7 +172,7 @@ impl ServiceDescription { ServiceDescriptionFormat::Internal => writer.write_all(self.to_string().as_bytes()), ServiceDescriptionFormat::Mie => { let mie = self.service2mie(); - let mie_str = serde_json::to_string(&mie).map_err(|e| { + let mie_str = serde_json::to_string_pretty(&mie).map_err(|e| { io::Error::other(format!("Error converting ServiceDescription to MIE: {e}")) })?; writer.write_all(mie_str.as_bytes()) @@ -169,6 +187,14 @@ impl ServiceDescription { } } +fn cnv_prefixmap(pm: &PrefixMap) -> HashMap { + let mut result = HashMap::new(); + for (alias, prefix) in pm.iter() { + result.insert(alias.clone(), prefix.clone()); + } + result +} + impl Display for ServiceDescription { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "Service")?; diff --git a/sparql_service/src/service_description_parser.rs b/sparql_service/src/service_description_parser.rs index 56154d17..b1f970ec 100644 --- a/sparql_service/src/service_description_parser.rs +++ b/sparql_service/src/service_description_parser.rs @@ -42,7 +42,7 @@ where let term = service_node.into(); self.rdf_parser.rdf.set_focus(&term); let service = Self::service_description().parse_impl(&mut self.rdf_parser.rdf)?; - Ok(service) + Ok(service.with_prefixmap(self.rdf_parser.prefixmap())) } pub fn service_description() -> impl RDFNodeParse From b5893e7c99b721fbca04cefbf559d957ff487353 Mon Sep 17 00:00:00 2001 From: Jose Labra Date: Tue, 23 Sep 2025 19:51:19 +0200 Subject: [PATCH 4/6] Release 0.1.104 mie@0.1.104 prefixmap@0.1.104 rudof_cli@0.1.104 shapes_converter@0.1.104 shex_compact@0.1.104 sparql_service@0.1.104 Generated by cargo-workspaces --- CHANGELOG.md | 12 ++++++++++++ examples/simple.shex | 2 +- mie/Cargo.toml | 2 +- prefixmap/Cargo.toml | 2 +- prefixmap/src/deref.rs | 2 +- prefixmap/src/iri_ref.rs | 2 +- rudof_cli/Cargo.toml | 2 +- shapes_converter/Cargo.toml | 2 +- shapes_converter/src/shex_to_uml/shex2uml.rs | 3 +-- shex_compact/Cargo.toml | 2 +- sparql_service/Cargo.toml | 2 +- 11 files changed, 22 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c37e7c70..3060a79a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ This ChangeLog follows the Keep a ChangeLog guidelines](https://keepachangelog.c ### Changed ### Removed +## 0.1.104 +### Added +- Added more information to MIE files + +### Fixed +- Tried to improve the error message when parsing ShEx files that have an undeclared alias according to issue #331 + +### Changed + +### Removed + + ## 0.1.103 ### Added diff --git a/examples/simple.shex b/examples/simple.shex index 38e6b273..575dbc77 100644 --- a/examples/simple.shex +++ b/examples/simple.shex @@ -1,7 +1,7 @@ prefix : prefix xsd: -:Person { pp:name xsd:string ; +:Person { :name xsd:string ; :birthdate xsd:date ? ; :enrolledIn @:Course * } diff --git a/mie/Cargo.toml b/mie/Cargo.toml index cc575045..59b25aa4 100755 --- a/mie/Cargo.toml +++ b/mie/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mie" -version = "0.1.102" +version = "0.1.104" authors.workspace = true description.workspace = true edition.workspace = true diff --git a/prefixmap/Cargo.toml b/prefixmap/Cargo.toml index f1714b77..87d9036a 100644 --- a/prefixmap/Cargo.toml +++ b/prefixmap/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prefixmap" -version = "0.1.91" +version = "0.1.104" authors.workspace = true description.workspace = true documentation = "https://docs.rs/prefixmap" diff --git a/prefixmap/src/deref.rs b/prefixmap/src/deref.rs index 60d04370..4daca578 100644 --- a/prefixmap/src/deref.rs +++ b/prefixmap/src/deref.rs @@ -13,7 +13,7 @@ pub enum DerefError { DerefPrefixMapError { alias: String, local: String, - error: PrefixMapError, + error: Box, }, #[error("No prefix map to dereference prefixed name {prefix}{local}")] diff --git a/prefixmap/src/iri_ref.rs b/prefixmap/src/iri_ref.rs index c6d92287..fa031c74 100644 --- a/prefixmap/src/iri_ref.rs +++ b/prefixmap/src/iri_ref.rs @@ -66,7 +66,7 @@ impl Deref for IriRef { DerefError::DerefPrefixMapError { alias: prefix.to_string(), local: local.to_string(), - error: e, + error: Box::new(e), } })?; Ok(IriRef::Iri(iri)) diff --git a/rudof_cli/Cargo.toml b/rudof_cli/Cargo.toml index 831fe795..0fb77f67 100755 --- a/rudof_cli/Cargo.toml +++ b/rudof_cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rudof_cli" -version = "0.1.102" +version = "0.1.104" authors.workspace = true description.workspace = true documentation = "https://rudof-project.github.io/rudof" diff --git a/shapes_converter/Cargo.toml b/shapes_converter/Cargo.toml index ea7b5ddd..56d07573 100755 --- a/shapes_converter/Cargo.toml +++ b/shapes_converter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shapes_converter" -version = "0.1.102" +version = "0.1.104" authors.workspace = true description.workspace = true documentation = "https://docs.rs/shapes_converter" diff --git a/shapes_converter/src/shex_to_uml/shex2uml.rs b/shapes_converter/src/shex_to_uml/shex2uml.rs index 40b27e10..c1e4ab95 100644 --- a/shapes_converter/src/shex_to_uml/shex2uml.rs +++ b/shapes_converter/src/shex_to_uml/shex2uml.rs @@ -92,11 +92,10 @@ impl ShEx2Uml { ShapeExpr::ShapeOr { shape_exprs } => { let cs: Vec<_> = shape_exprs .iter() - .map(|se| { + .flat_map(|se| { let c = self.shape_expr2component(name, &se.se, current_node_id)?; Ok::(c) }) - .flatten() .collect(); Ok(UmlComponent::or(cs.into_iter())) } diff --git a/shex_compact/Cargo.toml b/shex_compact/Cargo.toml index f038d740..02cf6b68 100755 --- a/shex_compact/Cargo.toml +++ b/shex_compact/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shex_compact" -version = "0.1.102" +version = "0.1.104" authors.workspace = true description.workspace = true documentation = "https://docs.rs/shex_compact" diff --git a/sparql_service/Cargo.toml b/sparql_service/Cargo.toml index 9ec014f0..fb4cd263 100755 --- a/sparql_service/Cargo.toml +++ b/sparql_service/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sparql_service" -version = "0.1.103" +version = "0.1.104" authors.workspace = true description.workspace = true edition.workspace = true From 246ad1605f33422f4f11f304a30051b376d56a88 Mon Sep 17 00:00:00 2001 From: Jose Labra Date: Wed, 24 Sep 2025 09:31:58 +0200 Subject: [PATCH 5/6] Updated oxigraph to 0.5.0 --- Cargo.toml | 16 +++++----- .../src/shex_to_uml/shex2uml_config.rs | 2 ++ shapes_converter/src/shex_to_uml/uml.rs | 30 ++++++++++++++++++- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 66dcf01d..3220e187 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,16 +86,16 @@ const_format = "0.2" indexmap = "2.1" oxsdatatypes = "0.2.2" oxiri = { version = "0.2.11" } -oxigraph = { version = "0.5.0-beta.2", default-features = false, features = [ +oxigraph = { version = "0.5.0", default-features = false, features = [ "rdf-12", ] } -oxrdf = { version = "0.3.0-beta.2", features = ["oxsdatatypes", "rdf-12"] } -oxrdfio = { version = "0.2.0-beta.2", features = ["rdf-12"] } -oxrdfxml = { version = "0.2.0-beta.2" } -oxttl = { version = "0.2.0-beta.2", features = ["rdf-12"] } -oxjsonld = { version = "0.2.0-beta.2", features = ["rdf-12"] } -sparesults = { version = "0.3.0-beta.2", features = ["sparql-12"] } -spargebra = { version = "0.4.0-beta.2", features = ["sparql-12"] } +oxrdf = { version = "0.3.0", features = ["oxsdatatypes", "rdf-12"] } +oxrdfio = { version = "0.2.0", features = ["rdf-12"] } +oxrdfxml = { version = "0.2.0" } +oxttl = { version = "0.2.0", features = ["rdf-12"] } +oxjsonld = { version = "0.2.0", features = ["rdf-12"] } +sparesults = { version = "0.3.0", features = ["sparql-12"] } +spargebra = { version = "0.4.0", features = ["sparql-12"] } oxilangtag = { version = "0.1.5", features = ["serde"] } regex = "1.11" supports-color = "3.0.0" diff --git a/shapes_converter/src/shex_to_uml/shex2uml_config.rs b/shapes_converter/src/shex_to_uml/shex2uml_config.rs index 0a15db06..c8ecafa6 100644 --- a/shapes_converter/src/shex_to_uml/shex2uml_config.rs +++ b/shapes_converter/src/shex_to_uml/shex2uml_config.rs @@ -17,6 +17,7 @@ pub struct ShEx2UmlConfig { pub plantuml_path: Option, pub annotation_label: Vec, pub replace_iri_by_label: Option, + pub shadowing: Option, pub shex: Option, } @@ -26,6 +27,7 @@ impl ShEx2UmlConfig { annotation_label: vec![IriS::new_unchecked(RDFS_LABEL_STR)], replace_iri_by_label: None, shex: Some(ShExConfig::default()), + shadowing: Some(true), plantuml_path: None, } } diff --git a/shapes_converter/src/shex_to_uml/uml.rs b/shapes_converter/src/shex_to_uml/uml.rs index ccad8d1c..ca60dcff 100644 --- a/shapes_converter/src/shex_to_uml/uml.rs +++ b/shapes_converter/src/shex_to_uml/uml.rs @@ -147,6 +147,7 @@ impl Uml { writer: &mut W, ) -> Result<(), UmlError> { writeln!(writer, "@startuml")?; + self.preamble(writer, config)?; for (node_id, component) in self.components.iter() { component2plantuml(node_id, component, config, writer)?; } @@ -167,6 +168,9 @@ impl Uml { target_node: &NodeId, ) -> Result<(), UmlError> { writeln!(writer, "@startuml")?; + self.preamble(writer, config)?; + + // Keep track of serialized components to avoid serializing them twice let mut serialized_components = HashSet::new(); // For all components in schema, check if they are neighbours with target_node @@ -195,6 +199,28 @@ impl Uml { writeln!(writer, "@enduml")?; Ok(()) } + + fn preamble(&self, writer: &mut impl Write, config: &ShEx2UmlConfig) -> Result<(), UmlError> { + writeln!(writer, "hide empty members")?; + + writeln!(writer, "skinparam linetype ortho")?; + + // Hide the class attribute icon + writeln!(writer, "hide circles")?; + + writeln!( + writer, + "skinparam shadowing {}", + config.shadowing.unwrap_or_default() + )?; + + // The following parameters should be taken from the ocnfig file... + writeln!(writer, "skinparam class {{")?; + writeln!(writer, " BorderColor Black")?; + writeln!(writer, " ArrowColor Black")?; + writeln!(writer, "}}")?; + Ok(()) + } } fn component2plantuml( @@ -228,7 +254,9 @@ fn component2plantuml( } writeln!(writer, "}}")?; } - UmlComponent::Or { exprs: _ } => todo!(), + UmlComponent::Or { exprs: _ } => { + writeln!(writer, "class \"OR\" as {node_id} {{}}")?; + } UmlComponent::Not { expr: _ } => todo!(), UmlComponent::And { exprs: _ } => todo!(), } From fa1e93f64e50310032df92c6324c52803183404d Mon Sep 17 00:00:00 2001 From: Jose Labra Date: Wed, 24 Sep 2025 09:32:23 +0200 Subject: [PATCH 6/6] Release 0.1.105 shapes_converter@0.1.105 Generated by cargo-workspaces --- shapes_converter/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shapes_converter/Cargo.toml b/shapes_converter/Cargo.toml index 56d07573..9651b0e2 100755 --- a/shapes_converter/Cargo.toml +++ b/shapes_converter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shapes_converter" -version = "0.1.104" +version = "0.1.105" authors.workspace = true description.workspace = true documentation = "https://docs.rs/shapes_converter"