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
« prev ^ index » next coverage.py v7.13.4, created at 2026-07-06 20:05 +0000
1#!/usr/bin/python
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
8# -*- coding: utf-8 -*-
9from __future__ import annotations
11from typing import TYPE_CHECKING
13from triplelite import RDFTerm
15from oc_ocdm.decorators import accepts_only
17if TYPE_CHECKING:
18 from typing import List, Optional
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
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."""
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!**
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.
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)
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)
60 de_list: List[DiscourseElement] = other.get_contained_discourse_elements()
61 for cur_de in de_list:
62 self.contains_discourse_element(cur_de)
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)
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)
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)
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)
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)
84 # HAS TITLE
85 def get_title(self) -> Optional[str]:
86 """
87 Getter method corresponding to the ``dcterms:title`` RDF predicate.
89 :return: The requested value if found, None otherwise
90 """
91 return self._get_literal(GraphEntity.iri_title)
93 def has_title(self, string: str) -> None:
94 """
95 Setter method corresponding to the ``dcterms:title`` RDF predicate.
97 **WARNING: this is a functional property, hence any existing value will be overwritten!**
99 `The title of the discourse element, such as the title of a figure or a section in an article.`
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)
109 def remove_title(self) -> None:
110 """
111 Remover method corresponding to the ``dcterms:title`` RDF predicate.
113 :return: None
114 """
115 self.g.remove((self.res, GraphEntity.iri_title, None))
117 # HAS PART (DiscourseElement)
118 def get_contained_discourse_elements(self) -> List[DiscourseElement]:
119 """
120 Getter method corresponding to the ``frbr:part`` RDF predicate.
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
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.
135 `The discourse element hierarchically nested within the parent element, such as a
136 sentence within a paragraph, or a paragraph within a section.`
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))))
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.
150 **WARNING: this is a non-functional property, hence, if the parameter
151 is None, any existing value will be removed!**
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))
164 # HAS NEXT (DiscourseElement)
165 def get_next_de(self) -> Optional[DiscourseElement]:
166 """
167 Getter method corresponding to the ``oco:hasNext`` RDF predicate.
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)
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.
180 **WARNING: this is a functional property, hence any existing value will be overwritten!**
182 `The following discourse element that includes at least one in-text reference pointer.`
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))))
192 def remove_next_de(self) -> None:
193 """
194 Remover method corresponding to the ``oco:hasNext`` RDF predicate.
196 :return: None
197 """
198 self.g.remove((self.res, GraphEntity.iri_has_next, None))
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.
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
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.
218 `Provides the textual and semantic context of the in-text reference pointer
219 that appears within the discourse element.`
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))))
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.
233 **WARNING: this is a non-functional property, hence, if the parameter
234 is None, any existing value will be removed!**
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))
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.
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
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.
265 `Provides the textual and semantic context of the list of
266 in-text reference pointers that appears within the discourse element.`
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))))
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.
280 **WARNING: this is a non-functional property, hence, if the parameter
281 is None, any existing value will be removed!**
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))
294 # HAS CONTENT
295 def get_content(self) -> Optional[str]:
296 """
297 Getter method corresponding to the ``c4o:hasContent`` RDF predicate.
299 :return: The requested value if found, None otherwise
300 """
301 return self._get_literal(GraphEntity.iri_has_content)
303 def has_content(self, string: str) -> None:
304 """
305 Setter method corresponding to the ``c4o:hasContent`` RDF predicate.
307 **WARNING: this is a functional property, hence any existing value will be overwritten!**
309 `The literal document text contained by the discourse element.`
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)
319 def remove_content(self) -> None:
320 """
321 Remover method corresponding to the ``c4o:hasContent`` RDF predicate.
323 :return: None
324 """
325 self.g.remove((self.res, GraphEntity.iri_has_content, None))
327 # HAS NUMBER
328 def get_number(self) -> Optional[str]:
329 """
330 Getter method corresponding to the ``fabio:hasSequenceIdentifier`` RDF predicate.
332 :return: The requested value if found, None otherwise
333 """
334 return self._get_literal(GraphEntity.iri_has_sequence_identifier)
336 def has_number(self, string: str) -> None:
337 """
338 Setter method corresponding to the ``fabio:hasSequenceIdentifier`` RDF predicate.
340 **WARNING: this is a functional property, hence any existing value will be overwritten!**
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)
350 def remove_number(self) -> None:
351 """
352 Remover method corresponding to the ``fabio:hasSequenceIdentifier`` RDF predicate.
354 :return: None
355 """
356 self.g.remove((self.res, GraphEntity.iri_has_sequence_identifier, None))
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``.
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!**
368 `The type of discourse element – such as “paragraph”, “section”, “sentence”,
369 “acknowledgements”, “reference list” or “figure”.`
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)
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``.
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!**
390 `The type of discourse element – such as “paragraph”, “section”, “sentence”,
391 “acknowledgements”, “reference list” or “figure”.`
393 :return: None
394 """
395 self._create_type(GraphEntity.iri_section)
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``.
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!**
406 `The type of discourse element – such as “paragraph”, “section”, “sentence”,
407 “acknowledgements”, “reference list” or “figure”.`
409 :return: None
410 """
411 self._create_type(GraphEntity.iri_section_title)
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``.
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!**
422 `The type of discourse element – such as “paragraph”, “section”, “sentence”,
423 “acknowledgements”, “reference list” or “figure”.`
425 :return: None
426 """
427 self._create_type(GraphEntity.iri_paragraph)
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``.
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!**
438 `The type of discourse element – such as “paragraph”, “section”, “sentence”,
439 “acknowledgements”, “reference list” or “figure”.`
441 :return: None
442 """
443 self._create_type(GraphEntity.iri_sentence)
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``.
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!**
454 `The type of discourse element – such as “paragraph”, “section”, “sentence”,
455 “acknowledgements”, “reference list” or “figure”.`
457 :return: None
458 """
459 self._create_type(GraphEntity.iri_text_chunk)
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``.
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!**
470 `The type of discourse element – such as “paragraph”, “section”, “sentence”,
471 “acknowledgements”, “reference list” or “figure”.`
473 :return: None
474 """
475 self._create_type(GraphEntity.iri_table)
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``.
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!**
486 `The type of discourse element – such as “paragraph”, “section”, “sentence”,
487 “acknowledgements”, “reference list” or “figure”.`
489 :return: None
490 """
491 self._create_type(GraphEntity.iri_footnote)
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``.
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!**
502 `The type of discourse element – such as “paragraph”, “section”, “sentence”,
503 “acknowledgements”, “reference list” or “figure”.`
505 :return: None
506 """
507 self._create_type(GraphEntity.iri_caption)
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``.
514 **WARNING: any existing rhetorical type WILL NOT be overwritten.**
515 """
517 create_type(self.g, self.res, GraphEntity.iri_introduction)
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``.
524 **WARNING: any existing rhetorical type WILL NOT be overwritten.**
525 """
526 create_type(self.g, self.res, GraphEntity.iri_methods)
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``.
533 **WARNING: any existing rhetorical type WILL NOT be overwritten.**
534 """
535 create_type(self.g, self.res, GraphEntity.iri_materials)
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``.
542 **WARNING: any existing rhetorical type WILL NOT be overwritten.**
543 """
544 create_type(self.g, self.res, GraphEntity.iri_related_work)
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``.
551 **WARNING: any existing rhetorical type WILL NOT be overwritten.**
552 """
553 create_type(self.g, self.res, GraphEntity.iri_results)
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``.
560 **WARNING: any existing rhetorical type WILL NOT be overwritten.**
561 """
562 create_type(self.g, self.res, GraphEntity.iri_discussion)
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``.
569 **WARNING: any existing rhetorical type WILL NOT be overwritten.**
570 """
571 create_type(self.g, self.res, GraphEntity.iri_conclusion)