Skip to content

Commit 57f5c54

Browse files
committed
fix(shacl): extract @base from JSON-LD for ontology parsing
1 parent 61ddbb5 commit 57f5c54

2 files changed

Lines changed: 66 additions & 4 deletions

File tree

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

0 commit comments

Comments
 (0)