Skip to content

Commit f5d3256

Browse files
authored
Merge pull request crs4#159 from kikkomep/fix/issue-137
fix: allow entity validation when @base is defined in @context
2 parents 517175d + 146a522 commit f5d3256

11 files changed

Lines changed: 1431 additions & 31 deletions

File tree

rocrate_validator/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1776,7 +1776,7 @@ def update(self, event: Event, ctx: Optional[ValidationContext] = None) -> None:
17761776
logger.debug("Validation ended with result: %s", event.validation_result)
17771777

17781778
def to_dict(self) -> dict:
1779-
""""
1779+
"""
17801780
Get the computed validation statistics as a dictionary
17811781
"""
17821782
return {

rocrate_validator/profiles/ro-crate/must/1_file-descriptor_metadata.ttl

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
1514
@prefix ro: <./> .
1615
@prefix ro-crate: <https://github.com/crs4/rocrate-validator/profiles/ro-crate/> .
1716
@prefix dct: <http://purl.org/dc/terms/> .
@@ -20,8 +19,31 @@
2019
@prefix sh: <http://www.w3.org/ns/shacl#> .
2120
@prefix validator: <https://github.com/crs4/rocrate-validator/> .
2221

22+
ro-crate:ROCrateMetadataFileDescriptorExistence a sh:NodeShape ;
23+
sh:targetNode sh:Graph ;
24+
sh:name "RO-Crate Metadata File Descriptor entity existence" ;
25+
sh:description """The RO-Crate JSON-LD MUST contain a Metadata File Descriptor entity named `ro-crate-metadata.json` and typed as `schema:CreativeWork`""" ;
26+
sh:sparql [
27+
a sh:SPARQLConstraint ;
28+
sh:name "RO-Crate Metadata File Descriptor entity existence" ;
29+
sh:description """Check if the RO-Crate Metadata File Descriptor entity exists,
30+
i.e., if there exists an entity with @id `ro-crate-metadata.json` and type `schema:CreativeWork`""" ;
31+
sh:message """The root of the document MUST have an entity with @id `ro-crate-metadata.json`, typed as `schema:CreativeWork` and with an `about` property referencing the Root Data Entity""" ;
32+
sh:prefixes ro-crate:sparqlPrefixes ;
33+
sh:select """
34+
SELECT ?this
35+
WHERE {
36+
FILTER NOT EXISTS {
37+
?crate a schema:Dataset .
38+
?md schema:about ?crate .
39+
?md a schema:CreativeWork .
40+
FILTER( STRENDS(STR(?md), "ro-crate-metadata.json") )
41+
}
42+
}
43+
""" ;
44+
] .
2345

24-
ro-crate:FindROCrateMetadataFileDescriptorEntity a sh:NodeShape, validator:HiddenShape;
46+
ro-crate:FindROCrateMetadataFileDescriptorEntity a sh:NodeShape, validator:HiddenShape ;
2547
sh:name "Identify the RO-Crate Metadata File Descriptor" ;
2648
sh:description """The RO-Crate Metadata File Descriptor entity describes the RO-Crate itself, and it is named as `ro-crate-metadata.json`.
2749
It can be identified by name according to the RO-Crate specification
@@ -32,7 +54,8 @@ ro-crate:FindROCrateMetadataFileDescriptorEntity a sh:NodeShape, validator:Hidde
3254
sh:select """
3355
SELECT ?this
3456
WHERE {
35-
?this a schema:CreativeWork ;
57+
?crate a schema:Dataset .
58+
?this schema:about ?crate .
3659
FILTER(contains(str(?this), "ro-crate-metadata.json"))
3760
}
3861
"""
@@ -46,29 +69,12 @@ ro-crate:FindROCrateMetadataFileDescriptorEntity a sh:NodeShape, validator:Hidde
4669
sh:object ro-crate:ROCrateMetadataFileDescriptor ;
4770
] .
4871

49-
ro-crate:ROCrateMetadataFileDescriptorExistence
50-
a sh:NodeShape ;
51-
sh:name "RO-Crate Metadata File Descriptor entity existence" ;
52-
sh:description "The RO-Crate JSON-LD MUST contain a Metadata File Descriptor entity named `ro-crate-metadata.json` and typed as `schema:CreativeWork`" ;
53-
sh:targetNode ro:ro-crate-metadata.json ;
54-
sh:property [
55-
a sh:PropertyShape ;
56-
sh:name "RO-Crate Metadata File Descriptor entity existence" ;
57-
sh:description """Check if the RO-Crate Metadata File Descriptor entity exists,
58-
i.e., if there exists an entity with @id `ro-crate-metadata.json` and type `schema:CreativeWork`""" ;
59-
sh:path rdf:type ;
60-
sh:hasValue ro-crate:ROCrateMetadataFileDescriptor ;
61-
sh:minCount 1 ;
62-
sh:message "The root of the document MUST have an entity with @id `ro-crate-metadata.json`" ;
63-
] .
64-
6572
ro-crate:ROCrateMetadataFileDescriptorRecommendedProperties a sh:NodeShape ;
6673
sh:name "RO-Crate Metadata File Descriptor REQUIRED properties" ;
6774
sh:description """RO-Crate Metadata Descriptor MUST be defined
68-
according with the requirements details defined in
69-
[RO-Crate Metadata File Descriptor](https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#ro-crate-metadata-file-descriptor)""";
70-
sh:targetNode ro:ro-crate-metadata.json ;
71-
sh:property [
75+
according with the requirements details defined in [RO-Crate Metadata File Descriptor](https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#ro-crate-metadata-file-descriptor)""" ;
76+
sh:targetClass ro-crate:ROCrateMetadataFileDescriptor ;
77+
sh:property [
7278
a sh:PropertyShape ;
7379
sh:name "Metadata File Descriptor entity type" ;
7480
sh:description "Check if the RO-Crate Metadata File Descriptor has `@type` CreativeWork, as per schema.org" ;
@@ -78,11 +84,11 @@ ro-crate:ROCrateMetadataFileDescriptorRecommendedProperties a sh:NodeShape ;
7884
sh:hasValue schema_org:CreativeWork ;
7985
sh:message "The RO-Crate metadata file MUST be a CreativeWork, as per schema.org" ;
8086
] ;
81-
sh:property [
87+
sh:property [
8288
a sh:PropertyShape ;
8389
sh:name "Metadata File Descriptor entity: `about` property" ;
8490
sh:description """Check if the RO-Crate Metadata File Descriptor has an `about` property referencing the Root Data Entity""" ;
85-
sh:maxCount 1;
91+
sh:maxCount 1 ;
8692
sh:minCount 1 ;
8793
sh:nodeKind sh:IRI ;
8894
sh:path schema_org:about ;

rocrate_validator/profiles/ro-crate/ontology.ttl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
# # # Classes
3232
# # #################################################################
3333

34+
# Declare the ROCrateMetadataFileDescriptor class
35+
ro-crate:ROCrateMetadataFileDescriptor rdf:type owl:Class ;
36+
rdfs:label "ROCrateMetadataFileDescriptor"@en .
37+
3438
# Declare the RootDataEntity class
3539
ro-crate:RootDataEntity rdf:type owl:Class ;
3640
rdfs:subClassOf schema:Dataset ;
@@ -65,3 +69,12 @@ ro-crate:DataEntity rdf:type owl:Class ;
6569
ro-crate:Directory rdf:type owl:Class ;
6670
rdfs:subClassOf schema:Dataset ;
6771
rdfs:label "Directory"@en .
72+
73+
74+
# # #################################################################
75+
# # # Individuals
76+
# # ##############################################################
77+
78+
# Declare the individual representing the RO-Crate Metadata File Descriptor entity
79+
ro:ro-crate-metadata.json a owl:NamedIndividual, ro-crate:ROCrateMetadataFileDescriptor .
80+

rocrate_validator/requirements/shacl/validator.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
from rdflib import BNode, Graph
2424
from rdflib.term import Node, URIRef
2525

26-
from rocrate_validator.utils import log as logging
2726
from rocrate_validator.constants import (DEFAULT_ONTOLOGY_FILE,
2827
RDF_SERIALIZATION_FORMATS,
2928
RDF_SERIALIZATION_FORMATS_TYPES,
@@ -34,6 +33,8 @@
3433
from rocrate_validator.requirements.shacl.models import ShapesRegistry
3534
from rocrate_validator.requirements.shacl.utils import (make_uris_relative,
3635
map_severity)
36+
from rocrate_validator.utils import log as logging
37+
from rocrate_validator.utils.rdf import extract_base_from_jsonld
3738

3839
# set up logging
3940
logger = logging.getLogger(__name__)
@@ -189,6 +190,22 @@ def __get_ontology_path__(self, profile_path: Path, ontology_filename: str = DEF
189190
self._ontology_path = Path(f"{profile_path}/{ontology_filename}")
190191
return self._ontology_path
191192

193+
def __get_data_graph_base__(self) -> Optional[str]:
194+
"""
195+
Get the @base from the RO-Crate metadata JSON-LD.
196+
197+
This extracts the @base from the @context of the data graph metadata,
198+
which can be used to align the ontology graph's base URI with the data graph.
199+
200+
:return: The @base value if found, None otherwise
201+
"""
202+
try:
203+
metadata_dict = self.ro_crate.metadata.as_dict()
204+
return extract_base_from_jsonld(metadata_dict)
205+
except Exception as e:
206+
logger.debug("Unable to extract @base from data graph metadata: %s", e)
207+
return None
208+
192209
def __load_ontology_graph__(self, profile_path: Path,
193210
ontology_filename: Optional[str] = DEFAULT_ONTOLOGY_FILE) -> Graph:
194211
# load the graph of ontologies
@@ -197,8 +214,20 @@ def __load_ontology_graph__(self, profile_path: Path,
197214
if os.path.exists(ontology_path):
198215
logger.debug("Loading ontologies: %s", ontology_path)
199216
ontology_graph = Graph()
217+
218+
# Determine the publicID to use:
219+
# 1. First, try to get @base from the data graph metadata
220+
# 2. Fall back to the default publicID (RO-Crate URI)
221+
data_graph_base = self.__get_data_graph_base__()
222+
public_id = data_graph_base if data_graph_base else self.publicID
223+
224+
if data_graph_base:
225+
logger.debug("Using @base from data graph metadata: %s", data_graph_base)
226+
else:
227+
logger.debug("Using default publicID: %s", self.publicID)
228+
200229
ontology_graph.parse(ontology_path, format="ttl",
201-
publicID=self.publicID)
230+
publicID=public_id)
202231
logger.debug("Ontologies loaded: %s", ontology_graph)
203232
return ontology_graph
204233

rocrate_validator/utils/rdf.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
from typing import Optional
1415

1516
from rdflib import Graph
16-
from rocrate_validator.utils import logger
17-
from rocrate_validator.utils.paths import list_graph_paths
1817

1918
from rocrate_validator import constants
19+
from rocrate_validator.utils import log as logging
20+
from rocrate_validator.utils.paths import list_graph_paths
21+
22+
# set up logging
23+
logger = logging.getLogger(__name__)
2024

2125

2226
def get_full_graph(
@@ -37,3 +41,32 @@ def get_full_graph(
3741
full_graph.parse(graph_path, format="turtle", publicID=publicID)
3842
logger.debug("Loaded triples from %s", graph_path)
3943
return full_graph
44+
45+
46+
def extract_base_from_jsonld(json_data: dict) -> Optional[str]:
47+
"""
48+
Extract the @base from the @context of a JSON-LD document.
49+
50+
The @context can be:
51+
- A dictionary (e.g., {"@base": "http://example.org/"})
52+
- A list of contexts (e.g., [{"@base": "http://example.org/"}, "https://schema.org"])
53+
54+
:param json_data: The JSON-LD data as a dictionary
55+
:return: The @base value if found, None otherwise
56+
"""
57+
context = json_data.get('@context')
58+
59+
if not context:
60+
return None
61+
62+
# If @context is a dictionary, look for @base directly
63+
if isinstance(context, dict):
64+
return context.get('@base')
65+
66+
# If @context is a list, look for @base in each context item
67+
if isinstance(context, list):
68+
for ctx in context:
69+
if isinstance(ctx, dict) and '@base' in ctx:
70+
return ctx['@base']
71+
72+
return None

tests/data/crates/valid/rocrate-with-at-base-set/example1/.gitkeep

Whitespace-only changes.

tests/data/crates/valid/rocrate-with-at-base-set/index.html

Whitespace-only changes.

0 commit comments

Comments
 (0)