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

139 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-05-08 20:23 +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 In case of functional properties, values from the current entity get overwritten 

42 by those coming from the second entity while, in all other cases, values from the 

43 second entity are simply appended to those of the current entity. In this context, 

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

45 

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

47 be merged into the current entity. 

48 :type other: DiscourseElement 

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

50 :return: None 

51 """ 

52 super()._merge_properties(other, prefer_self) 

53 assert isinstance(other, DiscourseElement) 

54 

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

56 if title is not None: 

57 self.has_title(title) 

58 

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

60 for cur_de in de_list: 

61 self.contains_discourse_element(cur_de) 

62 

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

64 if next_de is not None: 

65 self.has_next_de(next_de) 

66 

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

68 for cur_rp in rp_list: 

69 self.is_context_of_rp(cur_rp) 

70 

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

72 for cur_pl in pl_list: 

73 self.is_context_of_pl(cur_pl) 

74 

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

76 if content is not None: 

77 self.has_content(content) 

78 

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

80 if number is not None: 

81 self.has_number(number) 

82 

83 # HAS TITLE 

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

85 """ 

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

87 

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

89 """ 

90 return self._get_literal(GraphEntity.iri_title) 

91 

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

93 """ 

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

95 

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

97 

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

99 

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

101 :type string: str 

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

103 :return: None 

104 """ 

105 self.remove_title() 

106 self._create_literal(GraphEntity.iri_title, string) 

107 

108 def remove_title(self) -> None: 

109 """ 

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

111 

112 :return: None 

113 """ 

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

115 

116 # HAS PART (DiscourseElement) 

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

118 """ 

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

120 

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

122 """ 

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

124 result: List[DiscourseElement] = [] 

125 for uri in uri_list: 

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

127 return result 

128 

129 @accepts_only('de') 

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

131 """ 

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

133 

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

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

136 

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

138 :type de_res: DiscourseElement 

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

140 :return: None 

141 """ 

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

143 

144 @accepts_only('de') 

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

146 """ 

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

148 

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

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

151 

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

153 related to this method (defaults to None) 

154 :type de_res: DiscourseElement 

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

156 :return: None 

157 """ 

158 if de_res is not None: 

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

160 else: 

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

162 

163 # HAS NEXT (DiscourseElement) 

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

165 """ 

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

167 

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

169 """ 

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

171 if uri is not None: 

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

173 

174 @accepts_only('de') 

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

176 """ 

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

178 

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

180 

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

182 

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

184 :type de_res: DiscourseElement 

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

186 :return: None 

187 """ 

188 self.remove_next_de() 

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

190 

191 def remove_next_de(self) -> None: 

192 """ 

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

194 

195 :return: None 

196 """ 

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

198 

199 # IS CONTEXT OF (ReferencePointer) 

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

201 """ 

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

203 

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

205 """ 

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

207 result: List[ReferencePointer] = [] 

208 for uri in uri_list: 

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

210 return result 

211 

212 @accepts_only('rp') 

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

214 """ 

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

216 

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

218 that appears within the discourse element.` 

219 

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

221 :type rp_res: ReferencePointer 

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

223 :return: None 

224 """ 

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

226 

227 @accepts_only('rp') 

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

229 """ 

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

231 

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

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

234 

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

236 related to this method (defaults to None) 

237 :type rp_res: ReferencePointer 

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

239 :return: None 

240 """ 

241 if rp_res is not None: 

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

243 else: 

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

245 

246 # IS CONTEXT OF (PointerList) 

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

248 """ 

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

250 

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

252 """ 

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

254 result: List[PointerList] = [] 

255 for uri in uri_list: 

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

257 return result 

258 

259 @accepts_only('pl') 

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

261 """ 

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

263 

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

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

266 

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

268 :type pl_res: PointerList 

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

270 :return: None 

271 """ 

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

273 

274 @accepts_only('pl') 

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

276 """ 

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

278 

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

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

281 

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

283 related to this method (defaults to None) 

284 :type pl_res: PointerList 

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

286 :return: None 

287 """ 

288 if pl_res is not None: 

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

290 else: 

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

292 

293 # HAS CONTENT 

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

295 """ 

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

297 

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

299 """ 

300 return self._get_literal(GraphEntity.iri_has_content) 

301 

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

303 """ 

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

305 

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

307 

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

309 

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

311 :type string: str 

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

313 :return: None 

314 """ 

315 self.remove_content() 

316 self._create_literal(GraphEntity.iri_has_content, string) 

317 

318 def remove_content(self) -> None: 

319 """ 

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

321 

322 :return: None 

323 """ 

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

325 

326 # HAS NUMBER 

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

328 """ 

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

330 

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

332 """ 

333 return self._get_literal(GraphEntity.iri_has_sequence_identifier) 

334 

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

336 """ 

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

338 

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

340 

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

342 :type string: str 

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

344 :return: None 

345 """ 

346 self.remove_number() 

347 self._create_literal(GraphEntity.iri_has_sequence_identifier, string) 

348 

349 def remove_number(self) -> None: 

350 """ 

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

352 

353 :return: None 

354 """ 

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

356 

357 # HAS TYPE 

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

359 """ 

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

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

362 

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

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

365 will be overwritten!** 

366 

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

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

369 

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

371 :type de_class: URIRef 

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

373 :return: None 

374 """ 

375 if de_class is not None: 

376 self._create_type(de_class) 

377 else: 

378 self._create_type(GraphEntity.iri_discourse_element) 

379 

380 def create_section(self) -> None: 

381 """ 

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

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

384 

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

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

387 will be overwritten!** 

388 

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

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

391 

392 :return: None 

393 """ 

394 self._create_type(GraphEntity.iri_section) 

395 

396 def create_section_title(self) -> None: 

397 """ 

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

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

400 

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

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

403 will be overwritten!** 

404 

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

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

407 

408 :return: None 

409 """ 

410 self._create_type(GraphEntity.iri_section_title) 

411 

412 def create_paragraph(self) -> None: 

413 """ 

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

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

416 

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

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

419 will be overwritten!** 

420 

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

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

423 

424 :return: None 

425 """ 

426 self._create_type(GraphEntity.iri_paragraph) 

427 

428 def create_sentence(self) -> None: 

429 """ 

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

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

432 

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

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

435 will be overwritten!** 

436 

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

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

439 

440 :return: None 

441 """ 

442 self._create_type(GraphEntity.iri_sentence) 

443 

444 def create_text_chunk(self) -> None: 

445 """ 

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

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

448 

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

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

451 will be overwritten!** 

452 

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

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

455 

456 :return: None 

457 """ 

458 self._create_type(GraphEntity.iri_text_chunk) 

459 

460 def create_table(self) -> None: 

461 """ 

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

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

464 

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

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

467 will be overwritten!** 

468 

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

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

471 

472 :return: None 

473 """ 

474 self._create_type(GraphEntity.iri_table) 

475 

476 def create_footnote(self) -> None: 

477 """ 

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

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

480 

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

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

483 will be overwritten!** 

484 

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

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

487 

488 :return: None 

489 """ 

490 self._create_type(GraphEntity.iri_footnote) 

491 

492 def create_caption(self) -> None: 

493 """ 

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

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

496 

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

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

499 will be overwritten!** 

500 

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

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

503 

504 :return: None 

505 """ 

506 self._create_type(GraphEntity.iri_caption) 

507 

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)