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