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