Coverage for oc_ocdm / graph / entities / bibliographic / discourse_element.py: 94%

139 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-07-06 20:05 +0000

1#!/usr/bin/python 

2 

3# SPDX-FileCopyrightText: 2020-2022 Simone Persiani <iosonopersia@gmail.com> 

4# SPDX-FileCopyrightText: 2024 martasoricetti <marta.soricetti@studio.unibo.it> 

5# 

6# SPDX-License-Identifier: ISC 

7 

8# -*- coding: utf-8 -*- 

9from __future__ import annotations 

10 

11from typing import TYPE_CHECKING 

12 

13from triplelite import RDFTerm 

14 

15from oc_ocdm.decorators import accepts_only 

16 

17if TYPE_CHECKING: 

18 from typing import List, Optional 

19 

20 from oc_ocdm.graph.entities.bibliographic.pointer_list import PointerList 

21 from oc_ocdm.graph.entities.bibliographic.reference_pointer import ReferencePointer 

22from oc_ocdm.graph.entities.bibliographic_entity import BibliographicEntity 

23from oc_ocdm.graph.graph_entity import GraphEntity 

24from oc_ocdm.support.support import create_type 

25 

26 

27class DiscourseElement(BibliographicEntity): 

28 """Discourse element (short: de): a document component, either structural (e.g. 

29 paragraph, section, chapter, table, caption, footnote, title) or rhetorical (e.g. 

30 introduction, discussion, acknowledgements, reference list, figure, appendix), in which 

31 the content of a bibliographic resource can be organized.""" 

32 

33 def _merge_properties(self, other: GraphEntity, prefer_self: bool) -> None: 

34 """ 

35 The merge operation allows combining two ``DiscourseElement`` entities into a single one, 

36 by marking the second entity as to be deleted while also copying its data into the current 

37 ``DiscourseElement``. Moreover, every triple from the containing ``GraphSet`` referring to the second 

38 entity gets "redirected" to the current entity: **every other reference contained inside a 

39 different source (e.g. a triplestore) must be manually handled by the user!** 

40 

41 By default, functional values from the current entity get overwritten by values 

42 from the second entity. When ``prefer_self`` is True, existing functional values 

43 from the current entity are kept. Non-functional values from the second entity 

44 are appended to those of the current entity. In this context, 

45 ``rdfs:label`` is considered as a functional property, while ``rdf:type`` is not. 

46 

47 :param other: The entity which will be marked as to be deleted and whose properties will 

48 be merged into the current entity. 

49 :type other: DiscourseElement 

50 :raises TypeError: if the parameter is of the wrong type 

51 :return: None 

52 """ 

53 super()._merge_properties(other, prefer_self) 

54 assert isinstance(other, DiscourseElement) 

55 

56 title: Optional[str] = other.get_title() 

57 if title is not None and self._should_merge_functional_property(self.get_title(), prefer_self): 

58 self.has_title(title) 

59 

60 de_list: List[DiscourseElement] = other.get_contained_discourse_elements() 

61 for cur_de in de_list: 

62 self.contains_discourse_element(cur_de) 

63 

64 next_de: Optional[DiscourseElement] = other.get_next_de() 

65 if next_de is not None and self._should_merge_functional_property(self.get_next_de(), prefer_self): 

66 self.has_next_de(next_de) 

67 

68 rp_list: List[ReferencePointer] = other.get_is_context_of_rp() 

69 for cur_rp in rp_list: 

70 self.is_context_of_rp(cur_rp) 

71 

72 pl_list: List[PointerList] = other.get_is_context_of_pl() 

73 for cur_pl in pl_list: 

74 self.is_context_of_pl(cur_pl) 

75 

76 content: Optional[str] = other.get_content() 

77 if content is not None and self._should_merge_functional_property(self.get_content(), prefer_self): 

78 self.has_content(content) 

79 

80 number: Optional[str] = other.get_number() 

81 if number is not None and self._should_merge_functional_property(self.get_number(), prefer_self): 

82 self.has_number(number) 

83 

84 # HAS TITLE 

85 def get_title(self) -> Optional[str]: 

86 """ 

87 Getter method corresponding to the ``dcterms:title`` RDF predicate. 

88 

89 :return: The requested value if found, None otherwise 

90 """ 

91 return self._get_literal(GraphEntity.iri_title) 

92 

93 def has_title(self, string: str) -> None: 

94 """ 

95 Setter method corresponding to the ``dcterms:title`` RDF predicate. 

96 

97 **WARNING: this is a functional property, hence any existing value will be overwritten!** 

98 

99 `The title of the discourse element, such as the title of a figure or a section in an article.` 

100 

101 :param string: The value that will be set as the object of the property related to this method 

102 :type string: str 

103 :raises TypeError: if the parameter is of the wrong type 

104 :return: None 

105 """ 

106 self.remove_title() 

107 self._create_literal(GraphEntity.iri_title, string) 

108 

109 def remove_title(self) -> None: 

110 """ 

111 Remover method corresponding to the ``dcterms:title`` RDF predicate. 

112 

113 :return: None 

114 """ 

115 self.g.remove((self.res, GraphEntity.iri_title, None)) 

116 

117 # HAS PART (DiscourseElement) 

118 def get_contained_discourse_elements(self) -> List[DiscourseElement]: 

119 """ 

120 Getter method corresponding to the ``frbr:part`` RDF predicate. 

121 

122 :return: A list containing the requested values if found, None otherwise 

123 """ 

124 uri_list: List[str] = self._get_multiple_uri_references(GraphEntity.iri_contains_de, "de") 

125 result: List[DiscourseElement] = [] 

126 for uri in uri_list: 

127 result.append(self.g_set.add_de(self.resp_agent, self.source, uri)) 

128 return result 

129 

130 @accepts_only("de") 

131 def contains_discourse_element(self, de_res: DiscourseElement) -> None: 

132 """ 

133 Setter method corresponding to the ``frbr:part`` RDF predicate. 

134 

135 `The discourse element hierarchically nested within the parent element, such as a 

136 sentence within a paragraph, or a paragraph within a section.` 

137 

138 :param de_res: The value that will be set as the object of the property related to this method 

139 :type de_res: DiscourseElement 

140 :raises TypeError: if the parameter is of the wrong type 

141 :return: None 

142 """ 

143 self.g.add((self.res, GraphEntity.iri_contains_de, RDFTerm("uri", str(de_res.res)))) 

144 

145 @accepts_only("de") 

146 def remove_contained_discourse_element(self, de_res: DiscourseElement | None = None) -> None: 

147 """ 

148 Remover method corresponding to the ``frbr:part`` RDF predicate. 

149 

150 **WARNING: this is a non-functional property, hence, if the parameter 

151 is None, any existing value will be removed!** 

152 

153 :param de_res: If not None, the specific object value that will be removed from the property 

154 related to this method (defaults to None) 

155 :type de_res: DiscourseElement 

156 :raises TypeError: if the parameter is of the wrong type 

157 :return: None 

158 """ 

159 if de_res is not None: 

160 self.g.remove((self.res, GraphEntity.iri_contains_de, RDFTerm("uri", str(de_res.res)))) 

161 else: 

162 self.g.remove((self.res, GraphEntity.iri_contains_de, None)) 

163 

164 # HAS NEXT (DiscourseElement) 

165 def get_next_de(self) -> Optional[DiscourseElement]: 

166 """ 

167 Getter method corresponding to the ``oco:hasNext`` RDF predicate. 

168 

169 :return: The requested value if found, None otherwise 

170 """ 

171 uri: Optional[str] = self._get_uri_reference(GraphEntity.iri_has_next, "de") 

172 if uri is not None: 

173 return self.g_set.add_de(self.resp_agent, self.source, uri) 

174 

175 @accepts_only("de") 

176 def has_next_de(self, de_res: DiscourseElement) -> None: 

177 """ 

178 Setter method corresponding to the ``oco:hasNext`` RDF predicate. 

179 

180 **WARNING: this is a functional property, hence any existing value will be overwritten!** 

181 

182 `The following discourse element that includes at least one in-text reference pointer.` 

183 

184 :param de_res: The value that will be set as the object of the property related to this method 

185 :type de_res: DiscourseElement 

186 :raises TypeError: if the parameter is of the wrong type 

187 :return: None 

188 """ 

189 self.remove_next_de() 

190 self.g.add((self.res, GraphEntity.iri_has_next, RDFTerm("uri", str(de_res.res)))) 

191 

192 def remove_next_de(self) -> None: 

193 """ 

194 Remover method corresponding to the ``oco:hasNext`` RDF predicate. 

195 

196 :return: None 

197 """ 

198 self.g.remove((self.res, GraphEntity.iri_has_next, None)) 

199 

200 # IS CONTEXT OF (ReferencePointer) 

201 def get_is_context_of_rp(self) -> List[ReferencePointer]: 

202 """ 

203 Getter method corresponding to the ``c4o:isContextOf`` RDF predicate. 

204 

205 :return: A list containing the requested values if found, None otherwise 

206 """ 

207 uri_list: List[str] = self._get_multiple_uri_references(GraphEntity.iri_is_context_of, "rp") 

208 result: List[ReferencePointer] = [] 

209 for uri in uri_list: 

210 result.append(self.g_set.add_rp(self.resp_agent, self.source, uri)) 

211 return result 

212 

213 @accepts_only("rp") 

214 def is_context_of_rp(self, rp_res: ReferencePointer) -> None: 

215 """ 

216 Setter method corresponding to the ``c4o:isContextOf`` RDF predicate. 

217 

218 `Provides the textual and semantic context of the in-text reference pointer 

219 that appears within the discourse element.` 

220 

221 :param rp_res: The value that will be set as the object of the property related to this method 

222 :type rp_res: ReferencePointer 

223 :raises TypeError: if the parameter is of the wrong type 

224 :return: None 

225 """ 

226 self.g.add((self.res, GraphEntity.iri_is_context_of, RDFTerm("uri", str(rp_res.res)))) 

227 

228 @accepts_only("rp") 

229 def remove_is_context_of_rp(self, rp_res: ReferencePointer | None = None) -> None: 

230 """ 

231 Remover method corresponding to the ``c4o:isContextOf`` RDF predicate. 

232 

233 **WARNING: this is a non-functional property, hence, if the parameter 

234 is None, any existing value will be removed!** 

235 

236 :param rp_res: If not None, the specific object value that will be removed from the property 

237 related to this method (defaults to None) 

238 :type rp_res: ReferencePointer 

239 :raises TypeError: if the parameter is of the wrong type 

240 :return: None 

241 """ 

242 if rp_res is not None: 

243 self.g.remove((self.res, GraphEntity.iri_is_context_of, RDFTerm("uri", str(rp_res.res)))) 

244 else: 

245 self.g.remove((self.res, GraphEntity.iri_is_context_of, None)) 

246 

247 # IS CONTEXT OF (PointerList) 

248 def get_is_context_of_pl(self) -> List[PointerList]: 

249 """ 

250 Getter method corresponding to the ``c4o:isContextOf`` RDF predicate. 

251 

252 :return: A list containing the requested values if found, None otherwise 

253 """ 

254 uri_list: List[str] = self._get_multiple_uri_references(GraphEntity.iri_is_context_of, "pl") 

255 result: List[PointerList] = [] 

256 for uri in uri_list: 

257 result.append(self.g_set.add_pl(self.resp_agent, self.source, uri)) 

258 return result 

259 

260 @accepts_only("pl") 

261 def is_context_of_pl(self, pl_res: PointerList) -> None: 

262 """ 

263 Setter method corresponding to the ``c4o:isContextOf`` RDF predicate. 

264 

265 `Provides the textual and semantic context of the list of 

266 in-text reference pointers that appears within the discourse element.` 

267 

268 :param pl_res: The value that will be set as the object of the property related to this method 

269 :type pl_res: PointerList 

270 :raises TypeError: if the parameter is of the wrong type 

271 :return: None 

272 """ 

273 self.g.add((self.res, GraphEntity.iri_is_context_of, RDFTerm("uri", str(pl_res.res)))) 

274 

275 @accepts_only("pl") 

276 def remove_is_context_of_pl(self, pl_res: PointerList | None = None) -> None: 

277 """ 

278 Remover method corresponding to the ``c4o:isContextOf`` RDF predicate. 

279 

280 **WARNING: this is a non-functional property, hence, if the parameter 

281 is None, any existing value will be removed!** 

282 

283 :param pl_res: If not None, the specific object value that will be removed from the property 

284 related to this method (defaults to None) 

285 :type pl_res: PointerList 

286 :raises TypeError: if the parameter is of the wrong type 

287 :return: None 

288 """ 

289 if pl_res is not None: 

290 self.g.remove((self.res, GraphEntity.iri_is_context_of, RDFTerm("uri", str(pl_res.res)))) 

291 else: 

292 self.g.remove((self.res, GraphEntity.iri_is_context_of, None)) 

293 

294 # HAS CONTENT 

295 def get_content(self) -> Optional[str]: 

296 """ 

297 Getter method corresponding to the ``c4o:hasContent`` RDF predicate. 

298 

299 :return: The requested value if found, None otherwise 

300 """ 

301 return self._get_literal(GraphEntity.iri_has_content) 

302 

303 def has_content(self, string: str) -> None: 

304 """ 

305 Setter method corresponding to the ``c4o:hasContent`` RDF predicate. 

306 

307 **WARNING: this is a functional property, hence any existing value will be overwritten!** 

308 

309 `The literal document text contained by the discourse element.` 

310 

311 :param string: The value that will be set as the object of the property related to this method 

312 :type string: str 

313 :raises TypeError: if the parameter is of the wrong type 

314 :return: None 

315 """ 

316 self.remove_content() 

317 self._create_literal(GraphEntity.iri_has_content, string) 

318 

319 def remove_content(self) -> None: 

320 """ 

321 Remover method corresponding to the ``c4o:hasContent`` RDF predicate. 

322 

323 :return: None 

324 """ 

325 self.g.remove((self.res, GraphEntity.iri_has_content, None)) 

326 

327 # HAS NUMBER 

328 def get_number(self) -> Optional[str]: 

329 """ 

330 Getter method corresponding to the ``fabio:hasSequenceIdentifier`` RDF predicate. 

331 

332 :return: The requested value if found, None otherwise 

333 """ 

334 return self._get_literal(GraphEntity.iri_has_sequence_identifier) 

335 

336 def has_number(self, string: str) -> None: 

337 """ 

338 Setter method corresponding to the ``fabio:hasSequenceIdentifier`` RDF predicate. 

339 

340 **WARNING: this is a functional property, hence any existing value will be overwritten!** 

341 

342 :param string: The value that will be set as the object of the property related to this method 

343 :type string: str 

344 :raises TypeError: if the parameter is of the wrong type 

345 :return: None 

346 """ 

347 self.remove_number() 

348 self._create_literal(GraphEntity.iri_has_sequence_identifier, string) 

349 

350 def remove_number(self) -> None: 

351 """ 

352 Remover method corresponding to the ``fabio:hasSequenceIdentifier`` RDF predicate. 

353 

354 :return: None 

355 """ 

356 self.g.remove((self.res, GraphEntity.iri_has_sequence_identifier, None)) 

357 

358 # HAS TYPE 

359 def create_discourse_element(self, de_class: str | None = None) -> None: 

360 """ 

361 Setter method corresponding to the ``rdf:type`` RDF predicate. 

362 If parameter is None, it implicitly sets the object value ``deo:DiscourseElement``. 

363 

364 **WARNING: the OCDM specification admits at most two types for an entity. 

365 The main type cannot be edited or removed. Any existing secondary type 

366 will be overwritten!** 

367 

368 `The type of discourse element – such as “paragraph”, “section”, “sentence”, 

369 “acknowledgements”, “reference list” or “figure”.` 

370 

371 :param de_class: The value that will be set as the object of the property related to this method 

372 :type de_class: URIRef 

373 :raises TypeError: if the parameter is of the wrong type 

374 :return: None 

375 """ 

376 if de_class is not None: 

377 self._create_type(de_class) 

378 else: 

379 self._create_type(GraphEntity.iri_discourse_element) 

380 

381 def create_section(self) -> None: 

382 """ 

383 Setter method corresponding to the ``rdf:type`` RDF predicate. 

384 It implicitly sets the object value ``doco:Section``. 

385 

386 **WARNING: the OCDM specification admits at most two types for an entity. 

387 The main type cannot be edited or removed. Any existing secondary type 

388 will be overwritten!** 

389 

390 `The type of discourse element – such as “paragraph”, “section”, “sentence”, 

391 “acknowledgements”, “reference list” or “figure”.` 

392 

393 :return: None 

394 """ 

395 self._create_type(GraphEntity.iri_section) 

396 

397 def create_section_title(self) -> None: 

398 """ 

399 Setter method corresponding to the ``rdf:type`` RDF predicate. 

400 It implicitly sets the object value ``doco:SectionTitle``. 

401 

402 **WARNING: the OCDM specification admits at most two types for an entity. 

403 The main type cannot be edited or removed. Any existing secondary type 

404 will be overwritten!** 

405 

406 `The type of discourse element – such as “paragraph”, “section”, “sentence”, 

407 “acknowledgements”, “reference list” or “figure”.` 

408 

409 :return: None 

410 """ 

411 self._create_type(GraphEntity.iri_section_title) 

412 

413 def create_paragraph(self) -> None: 

414 """ 

415 Setter method corresponding to the ``rdf:type`` RDF predicate. 

416 It implicitly sets the object value ``doco:Paragraph``. 

417 

418 **WARNING: the OCDM specification admits at most two types for an entity. 

419 The main type cannot be edited or removed. Any existing secondary type 

420 will be overwritten!** 

421 

422 `The type of discourse element – such as “paragraph”, “section”, “sentence”, 

423 “acknowledgements”, “reference list” or “figure”.` 

424 

425 :return: None 

426 """ 

427 self._create_type(GraphEntity.iri_paragraph) 

428 

429 def create_sentence(self) -> None: 

430 """ 

431 Setter method corresponding to the ``rdf:type`` RDF predicate. 

432 It implicitly sets the object value ``doco:Sentence``. 

433 

434 **WARNING: the OCDM specification admits at most two types for an entity. 

435 The main type cannot be edited or removed. Any existing secondary type 

436 will be overwritten!** 

437 

438 `The type of discourse element – such as “paragraph”, “section”, “sentence”, 

439 “acknowledgements”, “reference list” or “figure”.` 

440 

441 :return: None 

442 """ 

443 self._create_type(GraphEntity.iri_sentence) 

444 

445 def create_text_chunk(self) -> None: 

446 """ 

447 Setter method corresponding to the ``rdf:type`` RDF predicate. 

448 It implicitly sets the object value ``doco:TextChunk``. 

449 

450 **WARNING: the OCDM specification admits at most two types for an entity. 

451 The main type cannot be edited or removed. Any existing secondary type 

452 will be overwritten!** 

453 

454 `The type of discourse element – such as “paragraph”, “section”, “sentence”, 

455 “acknowledgements”, “reference list” or “figure”.` 

456 

457 :return: None 

458 """ 

459 self._create_type(GraphEntity.iri_text_chunk) 

460 

461 def create_table(self) -> None: 

462 """ 

463 Setter method corresponding to the ``rdf:type`` RDF predicate. 

464 It implicitly sets the object value ``doco:Table``. 

465 

466 **WARNING: the OCDM specification admits at most two types for an entity. 

467 The main type cannot be edited or removed. Any existing secondary type 

468 will be overwritten!** 

469 

470 `The type of discourse element – such as “paragraph”, “section”, “sentence”, 

471 “acknowledgements”, “reference list” or “figure”.` 

472 

473 :return: None 

474 """ 

475 self._create_type(GraphEntity.iri_table) 

476 

477 def create_footnote(self) -> None: 

478 """ 

479 Setter method corresponding to the ``rdf:type`` RDF predicate. 

480 It implicitly sets the object value ``doco:Footnote``. 

481 

482 **WARNING: the OCDM specification admits at most two types for an entity. 

483 The main type cannot be edited or removed. Any existing secondary type 

484 will be overwritten!** 

485 

486 `The type of discourse element – such as “paragraph”, “section”, “sentence”, 

487 “acknowledgements”, “reference list” or “figure”.` 

488 

489 :return: None 

490 """ 

491 self._create_type(GraphEntity.iri_footnote) 

492 

493 def create_caption(self) -> None: 

494 """ 

495 Setter method corresponding to the ``rdf:type`` RDF predicate. 

496 It implicitly sets the object value ``deo:Caption``. 

497 

498 **WARNING: the OCDM specification admits at most two types for an entity. 

499 The main type cannot be edited or removed. Any existing secondary type 

500 will be overwritten!** 

501 

502 `The type of discourse element – such as “paragraph”, “section”, “sentence”, 

503 “acknowledgements”, “reference list” or “figure”.` 

504 

505 :return: None 

506 """ 

507 self._create_type(GraphEntity.iri_caption) 

508 

509 def create_introduction(self) -> None: 

510 """ 

511 Setter method corresponding to the ``rdf:type`` RDF predicate. 

512 It implicitly sets the object value ``deo:Introduction``. 

513 

514 **WARNING: any existing rhetorical type WILL NOT be overwritten.** 

515 """ 

516 

517 create_type(self.g, self.res, GraphEntity.iri_introduction) 

518 

519 def create_methods(self) -> None: 

520 """ 

521 Setter method corresponding to the ``rdf:type`` RDF predicate. 

522 It implicitly sets the object value ``deo:Methods``. 

523 

524 **WARNING: any existing rhetorical type WILL NOT be overwritten.** 

525 """ 

526 create_type(self.g, self.res, GraphEntity.iri_methods) 

527 

528 def create_materials(self) -> None: 

529 """ 

530 Setter method corresponding to the ``rdf:type`` RDF predicate. 

531 It implicitly sets the object value ``deo:Materials``. 

532 

533 **WARNING: any existing rhetorical type WILL NOT be overwritten.** 

534 """ 

535 create_type(self.g, self.res, GraphEntity.iri_materials) 

536 

537 def create_related_work(self) -> None: 

538 """ 

539 Setter method corresponding to the ``rdf:type`` RDF predicate. 

540 It implicitly sets the object value ``deo:RelatedWork``. 

541 

542 **WARNING: any existing rhetorical type WILL NOT be overwritten.** 

543 """ 

544 create_type(self.g, self.res, GraphEntity.iri_related_work) 

545 

546 def create_results(self) -> None: 

547 """ 

548 Setter method corresponding to the ``rdf:type`` RDF predicate. 

549 It implicitly sets the object value ``deo:Results``. 

550 

551 **WARNING: any existing rhetorical type WILL NOT be overwritten.** 

552 """ 

553 create_type(self.g, self.res, GraphEntity.iri_results) 

554 

555 def create_discussion(self) -> None: 

556 """ 

557 Setter method corresponding to the ``rdf:type`` RDF predicate. 

558 It implicitly sets the object value ``deo:Discussion``. 

559 

560 **WARNING: any existing rhetorical type WILL NOT be overwritten.** 

561 """ 

562 create_type(self.g, self.res, GraphEntity.iri_discussion) 

563 

564 def create_conclusion(self) -> None: 

565 """ 

566 Setter method corresponding to the ``rdf:type`` RDF predicate. 

567 It implicitly sets the object value ``deo:Conclusion``. 

568 

569 **WARNING: any existing rhetorical type WILL NOT be overwritten.** 

570 """ 

571 create_type(self.g, self.res, GraphEntity.iri_conclusion)