Skip to content

Commit da60f6e

Browse files
committed
recurse for turning templates into shapes
1 parent b64637e commit da60f6e

File tree

1 file changed

+127
-93
lines changed

1 file changed

+127
-93
lines changed

buildingmotif/utils.py

Lines changed: 127 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)