3232from rdflib import DCTERMS , RDF , RDFS , SH , XSD , Graph , Literal , Namespace , URIRef
3333from rdflib .namespace import split_uri
3434
35- from cmem_plugin_shapes .doc import SHAPES_DOC
36-
3735from . import __path__
3836
3937SHUI = Namespace ("https://vocab.eccenca.com/shui/" )
@@ -73,7 +71,65 @@ def str2bool(value: str) -> bool:
7371 label = PLUGIN_LABEL ,
7472 icon = Icon (file_name = "shapes.svg" , package = __package__ ),
7573 description = "Generate SHACL node and property shapes from a data graph" ,
76- documentation = SHAPES_DOC ,
74+ documentation = """This workflow task generates SHACL (Shapes Constraint Language)
75+ node and property shapes by analyzing instance data from a knowledge graph. The generated
76+ shapes describe the structure and properties of the classes used in the data graph.
77+
78+ ## Usage
79+
80+ The plugin analyzes an input data graph and creates:
81+
82+ - **Node shapes**: One for each class (`rdf:type`) used in the data graph
83+ - **Property shapes**: For all properties associated with each class, including:
84+ - Regular object properties (subject → object relationships)
85+ - Inverse object properties (object ← subject relationships, marked with ← prefix)
86+ - Datatype properties (literal values)
87+
88+ ## Output
89+
90+ The generated shapes are written to a shape catalog graph with:
91+
92+ - Unique URIs based on UUIDs (UUID5 derived from class/property IRIs)
93+ - Human-readable labels and names (using namespace prefixes when available)
94+ - Metadata including source data graph reference and timestamps
95+ - Optional plugin provenance information (see advanced options)
96+
97+ ## Example
98+
99+ Given a data graph with:
100+
101+ ``` turtle
102+ ex:Person123 a ex:Person ;
103+ ex:name "John" ;
104+ ex:knows ex:Person456 .
105+ ```
106+
107+ The plugin generates:
108+
109+ - A node shape for `ex:Person` with `sh:targetClass ex:Person`
110+
111+ ``` turtle
112+ graph:90ee6e27-59b1-5ac8-9d7a-116c60c6791a a sh:NodeShape ;
113+ rdfs:label "Person (ex:)"@en ;
114+ sh:name "Person (ex:)"@en ;
115+ sh:property
116+ graph:0fcf371d-f99a-5eeb-ab50-6e6b5fbb0e06 ,
117+ graph:dd5c6728-75a2-5215-8a5d-f9cd4077aaea ;
118+ sh:targetClass ex:Person .
119+ ```
120+
121+ - Property shapes for `ex:name` (datatype property) and `ex:knows` (object property)
122+
123+ ``` turtle
124+ graph:0fcf371d-f99a-5eeb-ab50-6e6b5fbb0e06 a sh:PropertyShape ;
125+ rdfs:label "knows (ex:)"@en ;
126+ sh:name "knows (ex:)"@en ;
127+ sh:nodeKind sh:IRI ;
128+ sh:path ex:knows ;
129+ shui:showAlways true .
130+ ```
131+
132+ """ ,
77133 parameters = [
78134 PluginParameter (
79135 param_type = GraphParameterType (allow_only_autocompleted_values = False ),
@@ -135,6 +191,13 @@ def str2bool(value: str) -> bool:
135191 description = "Provide the list of properties (as IRIs) to ignore." ,
136192 advanced = True ,
137193 ),
194+ PluginParameter (
195+ param_type = MultilineStringParameterType (),
196+ name = "ignore_types" ,
197+ label = "Types to ignore" ,
198+ description = "Provide the list of types (as IRIs) to ignore." ,
199+ advanced = True ,
200+ ),
138201 PluginParameter (
139202 param_type = BoolParameterType (),
140203 name = "plugin_provenance" ,
@@ -156,6 +219,7 @@ def __init__( # noqa: PLR0913
156219 import_shapes : bool = False ,
157220 prefix_cc : bool = False ,
158221 ignore_properties : str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" ,
222+ ignore_types : str = "" ,
159223 plugin_provenance : bool = False ,
160224 ) -> None :
161225 if not validators .url (data_graph_iri ):
@@ -191,6 +255,12 @@ def __init__( # noqa: PLR0913
191255 raise ValueError (f"Invalid property IRI ({ _ } ) in parameter 'Properties to ignore'" )
192256 self .ignore_properties .append (_ )
193257
258+ self .ignore_types = []
259+ for _ in filter (None , ignore_types .split ("\n " )):
260+ if not validators .url (_ ):
261+ raise ValueError (f"Invalid type IRI ({ _ } ) in parameter 'Types to ignore'" )
262+ self .ignore_types .append (_ )
263+
194264 self .plugin_provenance = plugin_provenance
195265
196266 self .shapes_count = 0
@@ -296,6 +366,7 @@ def get_class_dict(self) -> dict:
296366 ?subject a ?class .
297367 ?subject ?property ?object .
298368 { self .iri_list_to_filter (self .ignore_properties )}
369+ { self .iri_list_to_filter (self .ignore_types , name = "class" )}
299370 BIND(isLiteral(?object) AS ?data)
300371 BIND("false" AS ?inverse)
301372 }}
@@ -304,6 +375,7 @@ def get_class_dict(self) -> dict:
304375 ?object a ?class .
305376 ?subject ?property ?object .
306377 { self .iri_list_to_filter (self .ignore_properties )}
378+ { self .iri_list_to_filter (self .ignore_types , name = "class" )}
307379 BIND("false" AS ?data)
308380 BIND("true" AS ?inverse)
309381 }}
0 commit comments