diff --git a/pyshacl/constraints/constraint_component.py b/pyshacl/constraints/constraint_component.py index 6a3488e..ee14f15 100644 --- a/pyshacl/constraints/constraint_component.py +++ b/pyshacl/constraints/constraint_component.py @@ -4,6 +4,7 @@ https://www.w3.org/TR/shacl/#core-components-value-type """ import abc +import re import typing from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Set, Tuple @@ -117,8 +118,8 @@ def make_v_result_description( constraint_component=None, source_constraint=None, extra_messages: Optional[Iterable] = None, + bound_vars=None, ): - """ :param datagraph: :type datagraph: rdflib.Graph | rdflib.ConjunctiveGraph | rdflib.Dataset @@ -131,6 +132,7 @@ def make_v_result_description( :param messages: :type messages: List[str] :param result_path: + :param bound_vars: :param constraint_component: :param source_constraint: :param extra_messages: @@ -171,12 +173,18 @@ def make_v_result_description( if m in messages: continue if isinstance(m, Literal): - desc += "\tMessage: {}\n".format(str(m.value)) + msg = str(m.value) + if bound_vars is not None: + msg = self._format_sparql_based_result_message(msg, bound_vars) + desc += "\tMessage: {}\n".format(msg) else: # pragma: no cover desc += "\tMessage: {}\n".format(str(m)) for m in messages: if isinstance(m, Literal): - desc += "\tMessage: {}\n".format(str(m.value)) + msg = str(m.value) + if bound_vars is not None: + msg = self._format_sparql_based_result_message(msg, bound_vars) + desc += "\tMessage: {}\n".format(msg) else: # pragma: no cover desc += "\tMessage: {}\n".format(str(m)) return desc @@ -190,6 +198,7 @@ def make_v_result( constraint_component=None, source_constraint=None, extra_messages: Optional[Iterable] = None, + bound_vars=None, ): """ :param datagraph: @@ -199,6 +208,7 @@ def make_v_result( :param value_node: :type value_node: rdflib.term.Identifier | None :param result_path: + :param bound_vars: :param constraint_component: :param source_constraint: :param extra_messages: @@ -228,10 +238,20 @@ def make_v_result( for m in iter(extra_messages): if m in messages: continue + if isinstance(m, Literal): + msg = str(m.value) + if bound_vars is not None: + msg = self._format_sparql_based_result_message(msg, bound_vars) + m = Literal(msg) r_triples.append((r_node, SH_resultMessage, m)) elif not messages: messages = self.make_generic_messages(datagraph, focus_node, value_node) or messages for m in messages: + if isinstance(m, Literal): + msg = str(m.value) + if bound_vars is not None: + msg = self._format_sparql_based_result_message(msg, bound_vars) + m = Literal(msg) r_triples.append((r_node, SH_resultMessage, m)) desc = self.make_v_result_description( datagraph, @@ -243,10 +263,19 @@ def make_v_result( constraint_component=constraint_component, source_constraint=source_constraint, extra_messages=extra_messages, + bound_vars=bound_vars, ) self.shape.logger.debug(desc) return desc, r_node, r_triples + def _format_sparql_based_result_message(self, msg, bound_vars): + if bound_vars is None: + return msg + msg = re.sub('{[?$]this}', str(bound_vars[0]), msg) + msg = re.sub('{[?$]path}', str(bound_vars[1]), msg) + msg = re.sub('{[?$]value}', str(bound_vars[2]), msg) + return msg + SH_nodeValidator = SH.term('nodeValidator') SH_propertyValidator = SH.term('propertyValidator') diff --git a/pyshacl/constraints/sparql/sparql_based_constraints.py b/pyshacl/constraints/sparql/sparql_based_constraints.py index e0b1168..0561d30 100644 --- a/pyshacl/constraints/sparql/sparql_based_constraints.py +++ b/pyshacl/constraints/sparql/sparql_based_constraints.py @@ -136,7 +136,9 @@ def _evaluate_sparql_constraint(self, sparql_constraint, target_graph, f_v_dict) t, p, v = v if v is None: v = result_val - rept = self.make_v_result(target_graph, t or f, value_node=v, result_path=p, **rept_kwargs) + rept = self.make_v_result( + target_graph, t or f, value_node=v, result_path=p, bound_vars=(t, p, v), **rept_kwargs + ) else: rept = self.make_v_result(target_graph, f, value_node=v, **rept_kwargs) reports.append(rept)