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

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

63 

64 

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

66 form_fields = defaultdict(dict) 

67 

68 with open(os.path.join(os.path.dirname(__file__), "context.json"), "r") as config_file: 

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

70 

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

72 

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] 

85 

86 entity_key = (entity_type, subject_shape) 

87 

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) 

98 

99 if predicate not in form_fields[entity_key]: 

100 form_fields[entity_key][predicate] = [] 

101 

102 nodeShapes = [] 

103 if nodeShape: 

104 nodeShapes.append(nodeShape) 

105 nodeShapes.extend(orNodes) 

106 

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 

121 

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 } 

146 

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 ) 

156 

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) 

190 

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

192 

193 return form_fields 

194 

195 

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. 

201 

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. 

206 

207 Restituisce: 

208 list: Una lista di dizionari dei campi annidati. 

209 """ 

210 if processed_shapes is None: 

211 processed_shapes = set() 

212 

213 if shape_uri in processed_shapes: 

214 return [] 

215 

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

220 

221 temp_form_fields = process_query_results( 

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

223 ) 

224 

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) 

229 

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

234 

235 processed_shapes.remove(shape_uri) 

236 return nested_fields 

237 

238 

239def get_property_order(entity_type, display_rules): 

240 """ 

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

242 

243 Argomenti: 

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

245 

246 Restituisce: 

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

248 """ 

249 if not display_rules: 

250 return [] 

251 

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

262 

263 

264def order_fields(fields, property_order): 

265 """ 

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

267 

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. 

271 

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 

279 

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

282 

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 ) 

289 

290 

291def order_form_fields(form_fields, display_rules): 

292 """ 

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

294 

295 Argomenti: 

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

297 

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

307 

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] 

325 

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] 

344 

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 

366 

367 

368def apply_display_rules(shacl, form_fields, display_rules): 

369 """ 

370 Applica le regole di visualizzazione ai campi del form. 

371 

372 Argomenti: 

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

374 

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

382 

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 

400 

401 

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

403 """ 

404 Apply a display rule to a specific entity key. 

405  

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 ) 

437 

438 

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

443 

444 # Handle case where parent_prop is not a dictionary 

445 if not isinstance(parent_prop, dict): 

446 return nested_fields 

447 

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) 

454 

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 

471 

472 return result_fields 

473 

474 

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" 

481 

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

497 

498 

499def add_display_information(field_info, prop): 

500 """ 

501 Aggiunge informazioni di visualizzazione dal display_rules ad un campo. 

502 

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

521 

522 

523def handle_intermediate_relation(shacl, field_info, prop): 

524 """ 

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

526 

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

534 

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 ) 

548 

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 ) 

556 

557 connecting_property = next( 

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

559 ) 

560 

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

575 

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 } 

583 

584 

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. 

588 

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 

597 

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 ) 

608 

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 } 

628 

629 if "intermediateRelation" in original_field: 

630 new_field["intermediateRelation"] = original_field[ 

631 "intermediateRelation" 

632 ] 

633 

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 

640 

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) 

645 

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

647 

648 

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 

663 

664 

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 ) 

697 

698 results = execute_shacl_query( 

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

700 ) 

701 

702 # Prendiamo il primo risultato valido 

703 for row in results: 

704 if row.targetClass: 

705 return str(row.targetClass) 

706 return None 

707 

708 

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

710 """ 

711 Estrae i campi del form dalle shape SHACL. 

712 

713 Args: 

714 shacl: The SHACL graph 

715 display_rules: The display rules configuration 

716 app: Flask application instance 

717 

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

724 

725 processed_shapes = set() 

726 results = execute_shacl_query(shacl, COMMON_SPARQL_QUERY) 

727 results_list = list(results) 

728 

729 form_fields = process_query_results( 

730 shacl, results_list, display_rules, processed_shapes, app=app, depth=0 

731 ) 

732 

733 return form_fields 

734 

735 

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

737 """ 

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

739 

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. 

744 

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) 

752 

753 

754def extract_additional_properties(shacl, shape_uri): 

755 """ 

756 Estrae proprietà aggiuntive da una shape SHACL. 

757 

758 Argomenti: 

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

760 

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 ) 

776 

777 additional_properties_results = shacl.query( 

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

779 ) 

780 

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 

786 

787 return additional_properties