@@ -241,101 +241,135 @@ def get_template_parts_from_shape(
241241 body = Graph ()
242242 root_param = PARAM ["name" ]
243243
244- deps = []
245-
246- pshapes = shape_graph .objects (subject = shape_name , predicate = SH ["property" ])
247- for pshape in pshapes :
248- property_path = shape_graph .value (pshape , SH ["path" ])
249- if property_path is None :
250- raise Exception (
251- f"no sh:path detected on { shape_name } property shape { pshape } "
252- )
253- # TODO: expand otypes to include sh:in, sh:or, or no datatype at all!
254- otypes = list (
255- shape_graph .objects (
256- subject = pshape ,
257- predicate = SH ["qualifiedValueShape" ]
258- * ZeroOrOne # type:ignore
259- / (SH ["class" ] | SH ["node" ] | SH ["datatype" ]),
260- )
261- )
262- mincounts = list (
263- shape_graph .objects (
264- subject = pshape , predicate = SH ["minCount" ] | SH ["qualifiedMinCount" ]
244+ deps : List [Dict ] = []
245+ dep_keys : Set [Tuple [str , Optional [str ], str ]] = set ()
246+ visited : Set [Tuple [Node , Node ]] = set ()
247+
248+ def is_nodeshape (node_uri : URIRef ) -> bool :
249+ if (node_uri , RDF .type , SH .NodeShape ) in shape_graph :
250+ return True
251+ for graph in depedency_graphs .values ():
252+ if (node_uri , RDF .type , SH .NodeShape ) in graph :
253+ return True
254+ return False
255+
256+ def resolve_library (node_uri : URIRef ) -> Optional [str ]:
257+ library = None
258+ for library_name , graph in depedency_graphs .items ():
259+ if (node_uri , RDF .type , SH .NodeShape ) in graph :
260+ library = library_name
261+ break
262+ return library
263+
264+ def add_dependency (node_uri : URIRef , param : URIRef ):
265+ if not isinstance (node_uri , URIRef ):
266+ return
267+ library = resolve_library (node_uri ) if is_nodeshape (node_uri ) else None
268+ dep_key = (str (node_uri ), library , str (param ))
269+ if dep_key in dep_keys :
270+ return
271+ dep_dict : Dict [str , Union [str , Dict [str , URIRef ]]] = {
272+ "template" : str (node_uri ),
273+ "args" : {"name" : param },
274+ }
275+ if library is not None :
276+ dep_dict ["library" ] = library
277+ deps .append (dep_dict ) # type: ignore[arg-type]
278+ dep_keys .add (dep_key )
279+ body .add ((param , RDF .type , node_uri ))
280+
281+ def process_shape (shape_node : Node , focus_param : URIRef ):
282+ key = (shape_node , focus_param )
283+ if key in visited :
284+ return
285+ visited .add (key )
286+
287+ if (shape_node , RDF .type , OWL .Class ) in shape_graph and isinstance (
288+ shape_node , URIRef
289+ ):
290+ body .add ((focus_param , RDF .type , shape_node ))
291+
292+ for cls in shape_graph .objects (shape_node , SH ["class" ]):
293+ if isinstance (cls , URIRef ):
294+ body .add ((focus_param , RDF .type , cls ))
295+
296+ for cls in shape_graph .objects (shape_node , SH ["targetClass" ]):
297+ if isinstance (cls , URIRef ):
298+ body .add ((focus_param , RDF .type , cls ))
299+
300+ for node_shape in shape_graph .objects (shape_node , SH ["node" ]):
301+ if isinstance (node_shape , URIRef ):
302+ add_dependency (node_shape , focus_param )
303+
304+ pshapes = shape_graph .objects (subject = shape_node , predicate = SH ["property" ])
305+ for pshape in pshapes :
306+ property_path = shape_graph .value (pshape , SH ["path" ])
307+ if property_path is None :
308+ raise Exception (
309+ f"no sh:path detected on { shape_node } property shape { pshape } "
310+ )
311+
312+ mincounts = list (
313+ shape_graph .objects (
314+ subject = pshape , predicate = SH ["minCount" ] | SH ["qualifiedMinCount" ]
315+ )
265316 )
266- )
267- if len (otypes ) > 1 :
268- raise Exception (f"more than one object type detected on { shape_name } " )
269- if len (mincounts ) > 1 :
270- raise Exception (f"more than one min count detected on { shape_name } " )
271- if len (mincounts ) == 0 or len (otypes ) == 0 :
272- # print(f"No useful information on {shape_name} - {pshape}")
273- # print(shape_graph.cbd(pshape).serialize())
274- continue
275- (path , otype , mincount ) = property_path , otypes [0 ], mincounts [0 ]
276- assert isinstance (mincount , Literal )
277-
278- param_name = shape_graph .value (pshape , SH ["name" ])
279-
280- for num in range (int (mincount )):
281- if param_name is not None :
282- param = PARAM [f"{ param_name } { num } " ]
283- else :
284- param = _gensym ()
285- body .add ((root_param , path , param ))
286-
287- otype_as_class = (None , SH ["class" ], otype ) in shape_graph
288- otype_as_node = (None , SH ["node" ], otype ) in shape_graph
289- otype_is_nodeshape = (otype , RDF .type , SH .NodeShape ) in shape_graph
290-
291- if (otype_as_class and otype_is_nodeshape ) or otype_as_node :
292- if not isinstance (otype , URIRef ):
293- continue
294- library = None
295- for library_name , graph in depedency_graphs .items ():
296- if (otype , RDF .type , SH .NodeShape ) in graph :
297- library = library_name
298- break
299- if library is None :
300- deps .append ({"template" : str (otype ), "args" : {"name" : param }})
317+
318+ if len (mincounts ) > 1 :
319+ raise Exception (f"more than one min count detected on { shape_node } " )
320+ if len (mincounts ) == 0 :
321+ continue
322+
323+ mincount = mincounts [0 ]
324+ assert isinstance (mincount , Literal )
325+
326+ qvs_node = shape_graph .value (pshape , SH ["qualifiedValueShape" ])
327+
328+ def _gather (predicate ):
329+ if qvs_node is not None :
330+ return list (shape_graph .objects (qvs_node , predicate ))
331+ return list (shape_graph .objects (pshape , predicate ))
332+
333+ classes = _gather (SH ["class" ])
334+ nodes = _gather (SH ["node" ])
335+ datatypes = _gather (SH ["datatype" ])
336+
337+ types_total = len (classes ) + len (nodes ) + len (datatypes )
338+ if types_total > 1 :
339+ raise Exception (f"more than one object type detected on { shape_node } " )
340+ if types_total == 0 and qvs_node is None :
341+ continue
342+
343+ param_name = shape_graph .value (pshape , SH ["name" ])
344+
345+ for num in range (int (mincount .toPython ())):
346+ if param_name is not None :
347+ param = PARAM [f"{ param_name } { num } " ]
301348 else :
302- deps .append (
303- {
304- "template" : str (otype ),
305- "library" : library ,
306- "args" : {"name" : param },
307- }
308- )
309- body .add ((param , RDF .type , otype ))
310-
311- pvalue = shape_graph .value (pshape , SH ["hasValue" ])
312- if pvalue :
313- body .add ((root_param , path , pvalue ))
314-
315- if (shape_name , RDF .type , OWL .Class ) in shape_graph :
316- body .add ((root_param , RDF .type , shape_name ))
317-
318- classes = shape_graph .objects (shape_name , SH ["class" ])
319- for cls in classes :
320- body .add ((root_param , RDF .type , cls ))
321-
322- classes = shape_graph .objects (shape_name , SH ["targetClass" ])
323- for cls in classes :
324- body .add ((root_param , RDF .type , cls ))
325-
326- # for all objects of sh:node, add them to the deps if they haven't been added
327- # already through the property shapes above
328- nodes = shape_graph .cbd (shape_name ).objects (predicate = SH ["node" ], unique = True )
329- for node in nodes :
330- # if node is already in deps, skip it
331- if any (str (node ) == dep ["template" ] for dep in deps ):
332- continue
333- # skip non-URIRef nodes
334- if not isinstance (node , URIRef ):
335- continue
336- deps .append (
337- {"template" : str (node ), "args" : {"name" : "name" }}
338- ) # tie to root param
349+ param = _gensym ()
350+ body .add ((focus_param , property_path , param ))
351+
352+ if classes :
353+ for cls in classes :
354+ if isinstance (cls , URIRef ):
355+ if is_nodeshape (cls ):
356+ add_dependency (cls , param )
357+ else :
358+ body .add ((param , RDF .type , cls ))
359+
360+ if nodes :
361+ node_target = nodes [0 ]
362+ if isinstance (node_target , URIRef ):
363+ add_dependency (node_target , param )
364+
365+ if qvs_node is not None :
366+ process_shape (qvs_node , param )
367+
368+ pvalue = shape_graph .value (pshape , SH ["hasValue" ])
369+ if pvalue :
370+ body .add ((focus_param , property_path , pvalue ))
371+
372+ process_shape (shape_name , root_param )
339373
340374 return body , deps
341375
0 commit comments