Coverage for heritrace / utils / shacl_display.py: 97%
292 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-21 12:56 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-21 12:56 +0000
1# SPDX-FileCopyrightText: 2025 Arcangelo Massari <arcangelo.massari@unibo.it>
2#
3# SPDX-License-Identifier: ISC
5import json
6import os
7from collections import OrderedDict, defaultdict
8from typing import List
10from flask import Flask
11from heritrace.utils.filters import Filter
12from rdflib import Graph, URIRef
13from rdflib.plugins.sparql import prepareQuery
15COMMON_SPARQL_QUERY = prepareQuery(
16 """
17 SELECT ?shape ?type ?predicate ?nodeShape ?datatype ?maxCount ?minCount ?hasValue ?objectClass
18 ?conditionPath ?conditionValue ?pattern ?message
19 (GROUP_CONCAT(?optionalValue; separator=",") AS ?optionalValues)
20 (GROUP_CONCAT(?orNode; separator=",") AS ?orNodes)
21 WHERE {
22 ?shape sh:targetClass ?type ;
23 sh:property ?property .
24 ?property sh:path ?predicate .
25 OPTIONAL {
26 ?property sh:node ?nodeShape .
27 OPTIONAL {?nodeShape sh:targetClass ?objectClass .}
28 }
29 OPTIONAL {
30 ?property sh:or ?orList .
31 {
32 ?orList rdf:rest*/rdf:first ?orConstraint .
33 ?orConstraint sh:datatype ?datatype .
34 } UNION {
35 ?orList rdf:rest*/rdf:first ?orNodeShape .
36 ?orNodeShape sh:node ?orNode .
37 } UNION {
38 ?orList rdf:rest*/rdf:first ?orConstraint .
39 ?orConstraint sh:hasValue ?optionalValue .
40 }
41 }
42 OPTIONAL { ?property sh:datatype ?datatype . }
43 OPTIONAL { ?property sh:maxCount ?maxCount . }
44 OPTIONAL { ?property sh:minCount ?minCount . }
45 OPTIONAL { ?property sh:hasValue ?hasValue . }
46 OPTIONAL {
47 ?property sh:in ?list .
48 ?list rdf:rest*/rdf:first ?optionalValue .
49 }
50 OPTIONAL {
51 ?property sh:condition ?conditionNode .
52 ?conditionNode sh:path ?conditionPath ;
53 sh:hasValue ?conditionValue .
54 }
55 OPTIONAL { ?property sh:pattern ?pattern . }
56 OPTIONAL { ?property sh:message ?message . }
57 FILTER (isURI(?predicate))
58 }
59 GROUP BY ?shape ?type ?predicate ?nodeShape ?datatype ?maxCount ?minCount ?hasValue
60 ?objectClass ?conditionPath ?conditionValue ?pattern ?message
61""",
62 initNs={
63 "sh": "http://www.w3.org/ns/shacl#",
64 "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
65 },
66)
69def process_query_results(shacl, results, display_rules, processed_shapes, app: Flask, depth=0):
70 form_fields = defaultdict(dict)
72 with open(os.path.join(os.path.dirname(__file__), "context.json"), "r") as config_file:
73 context = json.load(config_file)["@context"]
75 custom_filter = Filter(context, display_rules, app.config['DATASET_DB_URL'])
77 for row in results:
78 subject_shape = str(row.shape)
79 entity_type = str(row.type)
80 predicate = str(row.predicate)
81 nodeShape = str(row.nodeShape) if row.nodeShape else None
82 hasValue = str(row.hasValue) if row.hasValue else None
83 objectClass = str(row.objectClass) if row.objectClass else None
84 minCount = 0 if row.minCount is None else int(row.minCount)
85 maxCount = None if row.maxCount is None else int(row.maxCount)
86 datatype = str(row.datatype) if row.datatype else None
87 optionalValues = [v for v in (row.optionalValues or "").split(",") if v]
88 orNodes = [v for v in (row.orNodes or "").split(",") if v]
90 entity_key = (entity_type, subject_shape)
92 condition_entry = {}
93 if row.conditionPath and row.conditionValue:
94 condition_entry["condition"] = {
95 "path": str(row.conditionPath),
96 "value": str(row.conditionValue),
97 }
98 if row.pattern:
99 condition_entry["pattern"] = str(row.pattern)
100 if row.message:
101 condition_entry["message"] = str(row.message)
103 if predicate not in form_fields[entity_key]:
104 form_fields[entity_key][predicate] = []
106 nodeShapes = []
107 if nodeShape:
108 nodeShapes.append(nodeShape)
109 nodeShapes.extend(orNodes)
111 existing_field = None
112 for field in form_fields[entity_key][predicate]:
113 if (
114 field.get("nodeShape") == nodeShape
115 and field.get("nodeShapes") == nodeShapes
116 and field.get("subjectShape") == subject_shape
117 and field.get("hasValue") == hasValue
118 and field.get("objectClass") == objectClass
119 and field.get("min") == minCount
120 and field.get("max") == maxCount
121 and field.get("optionalValues") == optionalValues
122 ):
123 existing_field = field
124 break
126 if existing_field:
127 # Aggiorniamo il campo esistente con nuovi datatype o condizioni
128 if datatype and str(datatype) not in existing_field.get("datatypes", []):
129 existing_field.setdefault("datatypes", []).append(str(datatype))
130 if condition_entry:
131 existing_field.setdefault("conditions", []).append(condition_entry)
132 else:
133 field_info = {
134 "entityType": entity_type,
135 "uri": predicate,
136 "nodeShape": nodeShape,
137 "nodeShapes": nodeShapes,
138 "subjectShape": subject_shape,
139 "entityKey": entity_key,
140 "datatypes": [datatype] if datatype else [],
141 "min": minCount,
142 "max": maxCount,
143 "hasValue": hasValue,
144 "objectClass": objectClass,
145 "optionalValues": optionalValues,
146 "conditions": [condition_entry] if condition_entry else [],
147 "inputType": determine_input_type(datatype),
148 "shouldBeDisplayed": True, # Default value
149 }
151 if nodeShape and nodeShape not in processed_shapes:
152 field_info["nestedShape"] = process_nested_shapes(
153 shacl,
154 display_rules,
155 nodeShape,
156 app,
157 depth=depth + 1,
158 processed_shapes=processed_shapes,
159 )
161 if orNodes:
162 field_info["or"] = []
163 for node in orNodes:
164 # Process orNode as a field_info
165 entity_type_or_node = get_shape_target_class(shacl, node)
166 object_class = get_object_class(shacl, node, predicate)
167 shape_display_name = custom_filter.human_readable_class(
168 (entity_type_or_node, node)
169 )
170 or_field_info = {
171 "entityType": entity_type_or_node,
172 "uri": predicate,
173 "displayName": shape_display_name,
174 "subjectShape": subject_shape,
175 "nodeShape": node,
176 "min": minCount,
177 "max": maxCount,
178 "hasValue": hasValue,
179 "objectClass": objectClass,
180 "optionalValues": optionalValues,
181 "conditions": [condition_entry] if condition_entry else [],
182 "shouldBeDisplayed": True, # Default value
183 }
184 if node not in processed_shapes:
185 or_field_info["nestedShape"] = process_nested_shapes(
186 shacl,
187 display_rules,
188 node,
189 app,
190 depth=depth + 1,
191 processed_shapes=processed_shapes,
192 )
193 field_info["or"].append(or_field_info)
195 form_fields[entity_key][predicate].append(field_info)
197 return form_fields
200def process_nested_shapes(
201 shacl: Graph, display_rules: List[dict], shape_uri: str, app: Flask, depth=0, processed_shapes=None
202):
203 """
204 Processa ricorsivamente le shape annidate.
206 Argomenti:
207 shape_uri (str): L'URI della shape da processare.
208 depth (int): La profondità corrente della ricorsione.
209 processed_shapes (set): Un insieme delle shape già processate.
211 Restituisce:
212 list: Una lista di dizionari dei campi annidati.
213 """
214 if processed_shapes is None:
215 processed_shapes = set()
217 if shape_uri in processed_shapes:
218 return []
220 processed_shapes.add(shape_uri)
221 init_bindings = {"shape": URIRef(shape_uri)}
222 nested_results = execute_shacl_query(shacl, COMMON_SPARQL_QUERY, init_bindings)
223 nested_fields = []
225 temp_form_fields = process_query_results(
226 shacl, nested_results, display_rules, processed_shapes, app=app, depth=depth
227 )
229 # Applica le regole di visualizzazione ai campi annidati
230 if display_rules:
231 temp_form_fields = apply_display_rules(shacl, temp_form_fields, display_rules)
232 temp_form_fields = order_form_fields(temp_form_fields, display_rules)
234 # Estrai i campi per il tipo di entità
235 for entity_type in temp_form_fields:
236 for predicate in temp_form_fields[entity_type]:
237 nested_fields.extend(temp_form_fields[entity_type][predicate])
239 processed_shapes.remove(shape_uri)
240 return nested_fields
243def get_property_order(entity_type, display_rules):
244 """
245 Recupera l'ordine delle proprietà per un tipo di entità dalle regole di visualizzazione.
247 Argomenti:
248 entity_type (str): L'URI del tipo di entità.
250 Restituisce:
251 list: Una lista di URI di proprietà nell'ordine desiderato.
252 """
253 if not display_rules:
254 return []
256 for rule in display_rules:
257 if rule.get("class") == entity_type and "propertyOrder" in rule:
258 return rule["propertyOrder"]
259 elif rule.get("class") == entity_type:
260 return [
261 prop.get("property") or prop.get("virtual_property")
262 for prop in rule.get("displayProperties", [])
263 if prop.get("property") or prop.get("virtual_property")
264 ]
265 return []
268def order_fields(fields, property_order):
269 """
270 Ordina i campi secondo l'ordine specificato delle proprietà.
272 Argomenti:
273 fields (list): Una lista di dizionari dei campi da ordinare.
274 property_order (list): Una lista di URI di proprietà nell'ordine desiderato.
276 Restituisce:
277 list: Una lista ordinata di dizionari dei campi.
278 """
279 if not fields:
280 return []
281 if not property_order:
282 return fields
284 # Create a dictionary to map predicates to their position in property_order
285 order_dict = {pred: i for i, pred in enumerate(property_order)}
287 # Sort fields based on their position in property_order
288 # Fields not in property_order will be placed at the end
289 return sorted(
290 fields,
291 key=lambda f: order_dict.get(f.get("predicate", f.get("uri", "")), float("inf")),
292 )
295def order_form_fields(form_fields, display_rules):
296 """
297 Ordina i campi del form secondo le regole di visualizzazione.
299 Argomenti:
300 form_fields (dict): I campi del form con possibili modifiche dalle regole di visualizzazione.
302 Restituisce:
303 OrderedDict: I campi del form ordinati.
304 """
305 ordered_form_fields = OrderedDict()
306 if display_rules:
307 for rule in display_rules:
308 target = rule.get("target", {})
309 entity_class = target.get("class")
310 entity_shape = target.get("shape")
312 # Case 1: Both class and shape are specified (exact match)
313 if entity_class and entity_shape:
314 entity_key = (entity_class, entity_shape)
315 if entity_key in form_fields:
316 ordered_properties = [
317 prop_rule.get("property") or prop_rule.get("virtual_property")
318 for prop_rule in rule.get("displayProperties", [])
319 if prop_rule.get("property") or prop_rule.get("virtual_property")
320 ]
321 ordered_form_fields[entity_key] = OrderedDict()
322 for prop in ordered_properties:
323 if prop in form_fields[entity_key]:
324 ordered_form_fields[entity_key][prop] = form_fields[entity_key][prop]
325 # Aggiungi le proprietà rimanenti non specificate nell'ordine
326 for prop in form_fields[entity_key]:
327 if prop not in ordered_properties:
328 ordered_form_fields[entity_key][prop] = form_fields[entity_key][prop]
330 # Case 2: Only class is specified (apply to all matching classes)
331 elif entity_class:
332 for key in form_fields:
333 if key[0] == entity_class: # Check if class part of tuple matches
334 entity_key = key
335 ordered_properties = [
336 prop_rule.get("property") or prop_rule.get("virtual_property")
337 for prop_rule in rule.get("displayProperties", [])
338 if prop_rule.get("property") or prop_rule.get("virtual_property")
339 ]
340 ordered_form_fields[entity_key] = OrderedDict()
341 for prop in ordered_properties:
342 if prop in form_fields[entity_key]:
343 ordered_form_fields[entity_key][prop] = form_fields[entity_key][prop]
344 # Aggiungi le proprietà rimanenti non specificate nell'ordine
345 for prop in form_fields[entity_key]:
346 if prop not in ordered_properties:
347 ordered_form_fields[entity_key][prop] = form_fields[entity_key][prop]
349 # Case 3: Only shape is specified (apply to all matching shapes)
350 elif entity_shape:
351 for key in form_fields:
352 if key[1] == entity_shape: # Check if shape part of tuple matches
353 entity_key = key
354 ordered_properties = [
355 prop_rule.get("property") or prop_rule.get("virtual_property")
356 for prop_rule in rule.get("displayProperties", [])
357 if prop_rule.get("property") or prop_rule.get("virtual_property")
358 ]
359 ordered_form_fields[entity_key] = OrderedDict()
360 for prop in ordered_properties:
361 if prop in form_fields[entity_key]:
362 ordered_form_fields[entity_key][prop] = form_fields[entity_key][prop]
363 # Aggiungi le proprietà rimanenti non specificate nell'ordine
364 for prop in form_fields[entity_key]:
365 if prop not in ordered_properties:
366 ordered_form_fields[entity_key][prop] = form_fields[entity_key][prop]
367 else:
368 ordered_form_fields = form_fields
369 return ordered_form_fields
372def apply_display_rules(shacl, form_fields, display_rules):
373 """
374 Applica le regole di visualizzazione ai campi del form.
376 Argomenti:
377 form_fields (dict): I campi del form iniziali estratti dalle shape SHACL.
379 Restituisce:
380 dict: I campi del form dopo aver applicato le regole di visualizzazione.
381 """
382 for rule in display_rules:
383 target = rule.get("target", {})
384 entity_class = target.get("class")
385 entity_shape = target.get("shape")
387 # Handle different cases based on available target information
388 # Case 1: Both class and shape are specified (exact match)
389 if entity_class and entity_shape:
390 entity_key = (entity_class, entity_shape)
391 if entity_key in form_fields:
392 apply_rule_to_entity(shacl, form_fields, entity_key, rule)
393 # Case 2: Only class is specified (apply to all matching classes)
394 elif entity_class:
395 for key in list(form_fields.keys()):
396 if key[0] == entity_class: # Check if class part of tuple matches
397 apply_rule_to_entity(shacl, form_fields, key, rule)
398 # Case 3: Only shape is specified (apply to all matching shapes)
399 elif entity_shape:
400 for key in list(form_fields.keys()):
401 if key[1] == entity_shape: # Check if shape part of tuple matches
402 apply_rule_to_entity(shacl, form_fields, key, rule)
403 return form_fields
406def apply_rule_to_entity(shacl, form_fields, entity_key, rule):
407 """
408 Apply a display rule to a specific entity key.
410 Args:
411 shacl: The SHACL graph
412 form_fields: The form fields dictionary
413 entity_key: The entity key tuple (class, shape)
414 rule: The display rule to apply
415 """
416 for prop in rule.get("displayProperties", []):
417 prop_uri = prop.get("property") or prop.get("virtual_property")
418 if prop_uri and prop_uri in form_fields[entity_key]:
419 for field_info in form_fields[entity_key][prop_uri]:
420 add_display_information(field_info, prop)
421 # Chiamata ricorsiva per le nestedShape
422 if "nestedShape" in field_info:
423 apply_display_rules_to_nested_shapes(
424 field_info["nestedShape"], prop, rule.get("target", {}).get("shape")
425 )
426 if "or" in field_info:
427 for or_field in field_info["or"]:
428 apply_display_rules_to_nested_shapes(
429 [or_field], field_info, rule.get("target", {}).get("shape")
430 )
431 if "intermediateRelation" in prop:
432 handle_intermediate_relation(shacl, field_info, prop)
433 if "displayRules" in prop:
434 handle_sub_display_rules(
435 shacl,
436 form_fields,
437 entity_key,
438 form_fields[entity_key][prop_uri],
439 prop,
440 )
443def apply_display_rules_to_nested_shapes(nested_fields, parent_prop, shape_uri):
444 """Apply display rules to nested shapes."""
445 if not nested_fields:
446 return []
448 # Handle case where parent_prop is not a dictionary
449 if not isinstance(parent_prop, dict):
450 return nested_fields
452 # Create a new list to avoid modifying the original
453 result_fields = []
454 for field in nested_fields:
455 # Create a copy of the field to avoid modifying the original
456 new_field = field.copy()
457 result_fields.append(new_field)
459 # Find the matching shape in the parent property's display rules
460 found_matching_shape = False
461 for rule in parent_prop.get("displayRules", []):
462 if rule.get("shape") == shape_uri and "nestedDisplayRules" in rule:
463 found_matching_shape = True
464 # Apply nested display rules to each field
465 for field in result_fields:
466 for nested_rule in rule["nestedDisplayRules"]:
467 # Check both predicate and uri keys to be more flexible
468 field_key = field.get("predicate", field.get("uri"))
469 if field_key == nested_rule["property"]:
470 # Apply display properties from the rule to the field
471 for key, value in nested_rule.items():
472 if key != "property":
473 field[key] = value
474 break
476 return result_fields
479def determine_input_type(datatype):
480 """
481 Determina il tipo di input appropriato basato sul datatype XSD.
482 """
483 if not datatype:
484 return "text"
486 datatype = str(datatype)
487 datatype_to_input = {
488 "http://www.w3.org/2001/XMLSchema#string": "text",
489 "http://www.w3.org/2001/XMLSchema#integer": "number",
490 "http://www.w3.org/2001/XMLSchema#decimal": "number",
491 "http://www.w3.org/2001/XMLSchema#float": "number",
492 "http://www.w3.org/2001/XMLSchema#double": "number",
493 "http://www.w3.org/2001/XMLSchema#boolean": "checkbox",
494 "http://www.w3.org/2001/XMLSchema#date": "date",
495 "http://www.w3.org/2001/XMLSchema#time": "time",
496 "http://www.w3.org/2001/XMLSchema#dateTime": "datetime-local",
497 "http://www.w3.org/2001/XMLSchema#anyURI": "url",
498 "http://www.w3.org/2001/XMLSchema#email": "email",
499 }
500 return datatype_to_input.get(datatype, "text")
503def add_display_information(field_info, prop):
504 """
505 Aggiunge informazioni di visualizzazione dal display_rules ad un campo.
507 Argomenti:
508 field_info (dict): Le informazioni del campo da aggiornare.
509 prop (dict): Le informazioni della proprietà dalle display_rules.
510 """
511 if "displayName" in prop:
512 field_info["displayName"] = prop["displayName"]
513 if "shouldBeDisplayed" in prop:
514 field_info["shouldBeDisplayed"] = prop.get("shouldBeDisplayed", True)
515 if "orderedBy" in prop:
516 field_info["orderedBy"] = prop["orderedBy"]
517 if "inputType" in prop:
518 field_info["inputType"] = prop["inputType"]
519 if "supportsSearch" in prop:
520 field_info["supportsSearch"] = prop["supportsSearch"]
521 if "minCharsForSearch" in prop:
522 field_info["minCharsForSearch"] = prop["minCharsForSearch"]
523 if "searchTarget" in prop:
524 field_info["searchTarget"] = prop["searchTarget"]
527def handle_intermediate_relation(shacl, field_info, prop):
528 """
529 Processa 'intermediateRelation' nelle display_rules e aggiorna il campo.
531 Argomenti:
532 field_info (dict): Le informazioni del campo da aggiornare.
533 prop (dict): Le informazioni della proprietà dalle display_rules.
534 """
535 intermediate_relation = prop["intermediateRelation"]
536 target_entity_type = intermediate_relation.get("targetEntityType")
537 intermediate_class = intermediate_relation.get("class")
539 connecting_property_query = prepareQuery(
540 """
541 SELECT ?property
542 WHERE {
543 ?shape sh:targetClass ?intermediateClass ;
544 sh:property ?propertyShape .
545 ?propertyShape sh:path ?property ;
546 sh:node ?targetNode .
547 ?targetNode sh:targetClass ?targetClass.
548 }
549 """,
550 initNs={"sh": "http://www.w3.org/ns/shacl#"},
551 )
553 connecting_property_results = shacl.query(
554 connecting_property_query,
555 initBindings={
556 "intermediateClass": URIRef(intermediate_class),
557 "targetClass": URIRef(target_entity_type),
558 },
559 )
561 connecting_property = next(
562 (str(row.property) for row in connecting_property_results), None
563 )
565 intermediate_properties = {}
566 target_shape = None
567 if "nestedShape" in field_info:
568 for nested_field in field_info["nestedShape"]:
569 if nested_field.get("uri") == connecting_property and "nestedShape" in nested_field:
570 if "nestedShape" in nested_field:
571 for target_field in nested_field["nestedShape"]:
572 uri = target_field.get("uri")
573 if uri:
574 if uri not in intermediate_properties:
575 intermediate_properties[uri] = []
576 intermediate_properties[uri].append(target_field)
577 if target_field.get("subjectShape"):
578 target_shape = target_field["subjectShape"]
580 field_info["intermediateRelation"] = {
581 "class": intermediate_class,
582 "targetEntityType": target_entity_type,
583 "targetShape": target_shape,
584 "connectingProperty": connecting_property,
585 "properties": intermediate_properties,
586 }
589def handle_sub_display_rules(shacl, form_fields, entity_key, field_info_list, prop):
590 """
591 Gestisce 'displayRules' nelle display_rules, applicando la regola corretta in base allo shape.
593 Argomenti:
594 form_fields (dict): I campi del form da aggiornare.
595 entity_key (tuple): La chiave dell'entità (class, shape).
596 field_info_list (list): Le informazioni del campo originale.
597 prop (dict): Le informazioni della proprietà dalle display_rules.
598 """
599 new_field_info_list = []
600 entity_class = entity_key[0] if isinstance(entity_key, tuple) else entity_key
602 for original_field in field_info_list:
603 # Trova la display rule corrispondente allo shape del campo
604 matching_rule = next(
605 (
606 rule
607 for rule in prop["displayRules"]
608 if rule["shape"] == original_field["nodeShape"]
609 ),
610 None,
611 )
613 if matching_rule:
614 new_field = {
615 "entityType": entity_class,
616 "entityKey": entity_key, # Store the tuple key
617 "objectClass": original_field.get("objectClass"),
618 "uri": prop["property"],
619 "datatype": original_field.get("datatype"),
620 "min": original_field.get("min"),
621 "max": original_field.get("max"),
622 "hasValue": original_field.get("hasValue"),
623 "nodeShape": original_field.get("nodeShape"),
624 "nodeShapes": original_field.get("nodeShapes"),
625 "subjectShape": original_field.get("subjectShape"),
626 "nestedShape": original_field.get("nestedShape"),
627 "displayName": matching_rule["displayName"],
628 "optionalValues": original_field.get("optionalValues", []),
629 "orderedBy": original_field.get("orderedBy"),
630 "or": original_field.get("or", []),
631 }
633 if "intermediateRelation" in original_field:
634 new_field["intermediateRelation"] = original_field[
635 "intermediateRelation"
636 ]
638 # Aggiungi proprietà aggiuntive dalla shape SHACL
639 if "shape" in matching_rule:
640 shape_uri = matching_rule["shape"]
641 additional_properties = extract_additional_properties(shacl, shape_uri)
642 if additional_properties:
643 new_field["additionalProperties"] = additional_properties
645 new_field_info_list.append(new_field)
646 else:
647 # Se non c'è una regola corrispondente, mantieni il campo originale
648 new_field_info_list.append(original_field)
650 form_fields[entity_key][prop["property"]] = new_field_info_list
653def get_shape_target_class(shacl, shape_uri):
654 query = prepareQuery(
655 """
656 SELECT ?targetClass
657 WHERE {
658 ?shape sh:targetClass ?targetClass .
659 }
660 """,
661 initNs={"sh": "http://www.w3.org/ns/shacl#"},
662 )
663 results = execute_shacl_query(shacl, query, {"shape": URIRef(shape_uri)})
664 for row in results:
665 return str(row.targetClass)
666 return None
669def get_object_class(shacl, shape_uri, predicate_uri):
670 query = prepareQuery(
671 """
672 SELECT DISTINCT ?targetClass
673 WHERE {
674 ?shape sh:property ?propertyShape .
675 ?propertyShape sh:path ?predicate .
676 {
677 # Caso 1: definizione diretta con sh:node
678 ?propertyShape sh:node ?nodeShape .
679 ?nodeShape sh:targetClass ?targetClass .
680 } UNION {
681 # Caso 2: definizione diretta con sh:class
682 ?propertyShape sh:class ?targetClass .
683 } UNION {
684 # Caso 3: definizione con sh:or che include node shapes
685 ?propertyShape sh:or ?orList .
686 ?orList rdf:rest*/rdf:first ?choice .
687 {
688 ?choice sh:node ?nodeShape .
689 ?nodeShape sh:targetClass ?targetClass .
690 } UNION {
691 ?choice sh:class ?targetClass .
692 }
693 }
694 }
695 """,
696 initNs={
697 "sh": "http://www.w3.org/ns/shacl#",
698 "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
699 },
700 )
702 results = execute_shacl_query(
703 shacl, query, {"shape": URIRef(shape_uri), "predicate": URIRef(predicate_uri)}
704 )
706 # Prendiamo il primo risultato valido
707 for row in results:
708 if row.targetClass:
709 return str(row.targetClass)
710 return None
713def extract_shacl_form_fields(shacl, display_rules, app: Flask):
714 """
715 Estrae i campi del form dalle shape SHACL.
717 Args:
718 shacl: The SHACL graph
719 display_rules: The display rules configuration
720 app: Flask application instance
722 Returns:
723 defaultdict: A dictionary where the keys are tuples (class, shape) and the values are dictionaries
724 of form fields with their properties.
725 """
726 if not shacl:
727 return dict()
729 processed_shapes = set()
730 results = execute_shacl_query(shacl, COMMON_SPARQL_QUERY)
731 results_list = list(results)
733 form_fields = process_query_results(
734 shacl, results_list, display_rules, processed_shapes, app=app, depth=0
735 )
737 return form_fields
740def execute_shacl_query(shacl: Graph, query, init_bindings=None):
741 """
742 Esegue una query SPARQL sul grafo SHACL con eventuali binding iniziali.
744 Args:
745 shacl (Graph): The SHACL graph on which to execute the query.
746 query (PreparedQuery): The prepared SPARQL query.
747 init_bindings (dict): Initial bindings for the query.
749 Returns:
750 Result: The query results.
751 """
752 if init_bindings:
753 return shacl.query(query, initBindings=init_bindings)
754 else:
755 return shacl.query(query)
758def extract_additional_properties(shacl, shape_uri):
759 """
760 Estrae proprietà aggiuntive da una shape SHACL.
762 Argomenti:
763 shape_uri (str): L'URI della shape SHACL.
765 Restituisce:
766 dict: Un dizionario delle proprietà aggiuntive.
767 """
768 additional_properties_query = prepareQuery(
769 """
770 SELECT ?predicate ?hasValue
771 WHERE {
772 ?shape a sh:NodeShape ;
773 sh:property ?property .
774 ?property sh:path ?predicate ;
775 sh:hasValue ?hasValue .
776 }
777 """,
778 initNs={"sh": "http://www.w3.org/ns/shacl#"},
779 )
781 additional_properties_results = shacl.query(
782 additional_properties_query, initBindings={"shape": URIRef(shape_uri)}
783 )
785 additional_properties = {}
786 for row in additional_properties_results:
787 predicate = str(row.predicate)
788 has_value = str(row.hasValue)
789 additional_properties[predicate] = has_value
791 return additional_properties