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

142 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-28 18:52 +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 oc_ocdm.decorators import accepts_only 

14 

15if TYPE_CHECKING: 

16 from typing import Optional, List 

17 from rdflib import URIRef 

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

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

20from oc_ocdm.graph.graph_entity import GraphEntity 

21from oc_ocdm.graph.entities.bibliographic_entity import BibliographicEntity 

22from oc_ocdm.support.support import create_type 

23 

24 

25class DiscourseElement(BibliographicEntity): 

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

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

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

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

30 

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

32 """ 

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

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

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

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

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

38 

39 In case of functional properties, values from the current entity get overwritten 

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

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

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

43 

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

45 be merged into the current entity. 

46 :type other: DiscourseElement 

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

48 :return: None 

49 """ 

50 super()._merge_properties(other, prefer_self) 

51 assert isinstance(other, DiscourseElement) 

52 

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

54 if title is not None: 

55 self.has_title(title) 

56 

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

58 for cur_de in de_list: 

59 self.contains_discourse_element(cur_de) 

60 

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

62 if next_de is not None: 

63 self.has_next_de(next_de) 

64 

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

66 for cur_rp in rp_list: 

67 self.is_context_of_rp(cur_rp) 

68 

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

70 for cur_pl in pl_list: 

71 self.is_context_of_pl(cur_pl) 

72 

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

74 if content is not None: 

75 self.has_content(content) 

76 

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

78 if number is not None: 

79 self.has_number(number) 

80 

81 # HAS TITLE 

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

83 """ 

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

85 

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

87 """ 

88 return self._get_literal(GraphEntity.iri_title) 

89 

90 @accepts_only('literal') 

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

92 """ 

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

94 

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

96 

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

98 

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

100 :type string: str 

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

102 :return: None 

103 """ 

104 self.remove_title() 

105 self._create_literal(GraphEntity.iri_title, string) 

106 

107 def remove_title(self) -> None: 

108 """ 

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

110 

111 :return: None 

112 """ 

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

114 

115 # HAS PART (DiscourseElement) 

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

117 """ 

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

119 

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

121 """ 

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

123 result: List[DiscourseElement] = [] 

124 for uri in uri_list: 

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

126 return result 

127 

128 @accepts_only('de') 

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

130 """ 

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

132 

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

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

135 

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

137 :type de_res: DiscourseElement 

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

139 :return: None 

140 """ 

141 self.g.add((self.res, GraphEntity.iri_contains_de, de_res.res)) 

142 

143 @accepts_only('de') 

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

145 """ 

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

147 

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

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

150 

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

152 related to this method (defaults to None) 

153 :type de_res: DiscourseElement 

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

155 :return: None 

156 """ 

157 if de_res is not None: 

158 self.g.remove((self.res, GraphEntity.iri_contains_de, de_res.res)) 

159 else: 

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

161 

162 # HAS NEXT (DiscourseElement) 

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

164 """ 

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

166 

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

168 """ 

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

170 if uri is not None: 

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

172 

173 @accepts_only('de') 

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

175 """ 

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

177 

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

179 

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

181 

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

183 :type de_res: DiscourseElement 

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

185 :return: None 

186 """ 

187 self.remove_next_de() 

188 self.g.add((self.res, GraphEntity.iri_has_next, de_res.res)) 

189 

190 def remove_next_de(self) -> None: 

191 """ 

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

193 

194 :return: None 

195 """ 

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

197 

198 # IS CONTEXT OF (ReferencePointer) 

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

200 """ 

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

202 

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

204 """ 

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

206 result: List[ReferencePointer] = [] 

207 for uri in uri_list: 

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

209 return result 

210 

211 @accepts_only('rp') 

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

213 """ 

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

215 

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

217 that appears within the discourse element.` 

218 

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

220 :type rp_res: ReferencePointer 

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

222 :return: None 

223 """ 

224 self.g.add((self.res, GraphEntity.iri_is_context_of, rp_res.res)) 

225 

226 @accepts_only('rp') 

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

228 """ 

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

230 

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

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

233 

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

235 related to this method (defaults to None) 

236 :type rp_res: ReferencePointer 

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

238 :return: None 

239 """ 

240 if rp_res is not None: 

241 self.g.remove((self.res, GraphEntity.iri_is_context_of, rp_res.res)) 

242 else: 

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

244 

245 # IS CONTEXT OF (PointerList) 

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

247 """ 

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

249 

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

251 """ 

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

253 result: List[PointerList] = [] 

254 for uri in uri_list: 

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

256 return result 

257 

258 @accepts_only('pl') 

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

260 """ 

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

262 

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

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

265 

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

267 :type pl_res: PointerList 

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

269 :return: None 

270 """ 

271 self.g.add((self.res, GraphEntity.iri_is_context_of, pl_res.res)) 

272 

273 @accepts_only('pl') 

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

275 """ 

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

277 

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

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

280 

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

282 related to this method (defaults to None) 

283 :type pl_res: PointerList 

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

285 :return: None 

286 """ 

287 if pl_res is not None: 

288 self.g.remove((self.res, GraphEntity.iri_is_context_of, pl_res.res)) 

289 else: 

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

291 

292 # HAS CONTENT 

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

294 """ 

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

296 

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

298 """ 

299 return self._get_literal(GraphEntity.iri_has_content) 

300 

301 @accepts_only('literal') 

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 @accepts_only('literal') 

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 @accepts_only('thing') 

360 def create_discourse_element(self, de_class: URIRef | None = None) -> None: 

361 """ 

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

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

364 

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

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

367 will be overwritten!** 

368 

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

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

371 

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

373 :type de_class: URIRef 

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

375 :return: None 

376 """ 

377 if de_class is not None: 

378 self._create_type(de_class) 

379 else: 

380 self._create_type(GraphEntity.iri_discourse_element) 

381 

382 def create_section(self) -> None: 

383 """ 

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

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

386 

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

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

389 will be overwritten!** 

390 

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

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

393 

394 :return: None 

395 """ 

396 self._create_type(GraphEntity.iri_section) 

397 

398 def create_section_title(self) -> None: 

399 """ 

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

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

402 

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

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

405 will be overwritten!** 

406 

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

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

409 

410 :return: None 

411 """ 

412 self._create_type(GraphEntity.iri_section_title) 

413 

414 def create_paragraph(self) -> None: 

415 """ 

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

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

418 

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

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

421 will be overwritten!** 

422 

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

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

425 

426 :return: None 

427 """ 

428 self._create_type(GraphEntity.iri_paragraph) 

429 

430 def create_sentence(self) -> None: 

431 """ 

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

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

434 

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

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

437 will be overwritten!** 

438 

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

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

441 

442 :return: None 

443 """ 

444 self._create_type(GraphEntity.iri_sentence) 

445 

446 def create_text_chunk(self) -> None: 

447 """ 

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

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

450 

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

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

453 will be overwritten!** 

454 

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

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

457 

458 :return: None 

459 """ 

460 self._create_type(GraphEntity.iri_text_chunk) 

461 

462 def create_table(self) -> None: 

463 """ 

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

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

466 

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

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

469 will be overwritten!** 

470 

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

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

473 

474 :return: None 

475 """ 

476 self._create_type(GraphEntity.iri_table) 

477 

478 def create_footnote(self) -> None: 

479 """ 

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

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

482 

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

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

485 will be overwritten!** 

486 

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

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

489 

490 :return: None 

491 """ 

492 self._create_type(GraphEntity.iri_footnote) 

493 

494 def create_caption(self) -> None: 

495 """ 

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

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

498 

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

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

501 will be overwritten!** 

502 

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

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

505 

506 :return: None 

507 """ 

508 self._create_type(GraphEntity.iri_caption) 

509 

510 

511 def create_introduction(self) -> None: 

512 """ 

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

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

515 

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

517 """ 

518 

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

520 

521 def create_methods(self) -> None: 

522 """ 

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

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

525 

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

527 """ 

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

529 

530 def create_materials(self) -> None: 

531 """ 

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

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

534 

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

536 """ 

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

538 

539 def create_related_work(self) -> None: 

540 """ 

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

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

543 

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

545 """ 

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

547 

548 def create_results(self) -> None: 

549 """ 

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

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

552 

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

554 """ 

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

556 

557 def create_discussion(self) -> None: 

558 """ 

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

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

561 

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

563 """ 

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

565 

566 def create_conclusion(self) -> None: 

567 """ 

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

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

570 

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

572 """ 

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