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

1import json 

2import os 

3from collections import OrderedDict, defaultdict 

4from typing import List 

5 

6from flask import Flask 

7from heritrace.utils.filters import Filter 

8from rdflib import Graph, URIRef 

9from rdflib.plugins.sparql import prepareQuery 

10 

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) 

60 

61 

62def process_query_results(shacl, results, display_rules, processed_shapes, app: Flask, depth=0): 

63 form_fields = defaultdict(dict) 

64 

65 with open(os.path.join("resources", "context.json"), "r") as config_file: 

66 context = json.load(config_file)["@context"] 

67 

68 custom_filter = Filter(context, display_rules, app.config['DATASET_DB_URL']) 

69 

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] 

82 

83 entity_key = (entity_type, subject_shape) 

84 

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) 

95 

96 if predicate not in form_fields[entity_key]: 

97 form_fields[entity_key][predicate] = [] 

98 

99 nodeShapes = [] 

100 if nodeShape: 

101 nodeShapes.append(nodeShape) 

102 nodeShapes.extend(orNodes) 

103 

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 

118 

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 } 

142 

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 ) 

152 

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) 

185 

186 form_fields[entity_key][predicate].append(field_info) 

187 

188 return form_fields 

189 

190 

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. 

196 

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. 

201 

202 Restituisce: 

203 list: Una lista di dizionari dei campi annidati. 

204 """ 

205 if processed_shapes is None: 

206 processed_shapes = set() 

207 

208 if shape_uri in processed_shapes: 

209 return [] 

210 

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 = [] 

215 

216 temp_form_fields = process_query_results( 

217 shacl, nested_results, display_rules, processed_shapes, app=app, depth=depth 

218 ) 

219 

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) 

224 

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]) 

229 

230 processed_shapes.remove(shape_uri) 

231 return nested_fields 

232 

233 

234def get_property_order(entity_type, display_rules): 

235 """ 

236 Recupera l'ordine delle proprietà per un tipo di entità dalle regole di visualizzazione. 

237 

238 Argomenti: 

239 entity_type (str): L'URI del tipo di entità. 

240 

241 Restituisce: 

242 list: Una lista di URI di proprietà nell'ordine desiderato. 

243 """ 

244 if not display_rules: 

245 return [] 

246 

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 [] 

253 

254 

255def order_fields(fields, property_order): 

256 """ 

257 Ordina i campi secondo l'ordine specificato delle proprietà. 

258 

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. 

262 

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 

270 

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)} 

273 

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 ) 

280 

281 

282def order_form_fields(form_fields, display_rules): 

283 """ 

284 Ordina i campi del form secondo le regole di visualizzazione. 

285 

286 Argomenti: 

287 form_fields (dict): I campi del form con possibili modifiche dalle regole di visualizzazione. 

288 

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") 

298 

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] 

315 

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] 

333 

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 

354 

355 

356def apply_display_rules(shacl, form_fields, display_rules): 

357 """ 

358 Applica le regole di visualizzazione ai campi del form. 

359 

360 Argomenti: 

361 form_fields (dict): I campi del form iniziali estratti dalle shape SHACL. 

362 

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") 

370 

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 

388 

389 

390def apply_rule_to_entity(shacl, form_fields, entity_key, rule): 

391 """ 

392 Apply a display rule to a specific entity key. 

393  

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 ) 

425 

426 

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 [] 

431 

432 # Handle case where parent_prop is not a dictionary 

433 if not isinstance(parent_prop, dict): 

434 return nested_fields 

435 

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) 

442 

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 

459 

460 return result_fields 

461 

462 

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" 

469 

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") 

485 

486 

487def add_display_information(field_info, prop): 

488 """ 

489 Aggiunge informazioni di visualizzazione dal display_rules ad un campo. 

490 

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"] 

509 

510 

511def handle_intermediate_relation(shacl, field_info, prop): 

512 """ 

513 Processa 'intermediateRelation' nelle display_rules e aggiorna il campo. 

514 

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") 

522 

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 ) 

536 

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 ) 

544 

545 connecting_property = next( 

546 (str(row.property) for row in connecting_property_results), None 

547 ) 

548 

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"] 

563 

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 } 

571 

572 

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. 

576 

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 

585 

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 ) 

596 

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 } 

616 

617 if "intermediateRelation" in original_field: 

618 new_field["intermediateRelation"] = original_field[ 

619 "intermediateRelation" 

620 ] 

621 

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 

628 

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) 

633 

634 form_fields[entity_key][prop["property"]] = new_field_info_list 

635 

636 

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 

651 

652 

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 ) 

685 

686 results = execute_shacl_query( 

687 shacl, query, {"shape": URIRef(shape_uri), "predicate": URIRef(predicate_uri)} 

688 ) 

689 

690 # Prendiamo il primo risultato valido 

691 for row in results: 

692 if row.targetClass: 

693 return str(row.targetClass) 

694 return None 

695 

696 

697def extract_shacl_form_fields(shacl, display_rules, app: Flask): 

698 """ 

699 Estrae i campi del form dalle shape SHACL. 

700 

701 Args: 

702 shacl: The SHACL graph 

703 display_rules: The display rules configuration 

704 app: Flask application instance 

705 

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() 

712 

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 

719 

720 

721def execute_shacl_query(shacl: Graph, query, init_bindings=None): 

722 """ 

723 Esegue una query SPARQL sul grafo SHACL con eventuali binding iniziali. 

724 

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. 

729 

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) 

737 

738 

739def extract_additional_properties(shacl, shape_uri): 

740 """ 

741 Estrae proprietà aggiuntive da una shape SHACL. 

742 

743 Argomenti: 

744 shape_uri (str): L'URI della shape SHACL. 

745 

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 ) 

761 

762 additional_properties_results = shacl.query( 

763 additional_properties_query, initBindings={"shape": URIRef(shape_uri)} 

764 ) 

765 

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 

771 

772 return additional_properties