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

66 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# 

5# SPDX-License-Identifier: ISC 

6 

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

8from __future__ import annotations 

9 

10import re 

11from typing import TYPE_CHECKING 

12 

13if TYPE_CHECKING: 

14 from typing import Optional 

15 from rdflib import URIRef 

16 

17from oc_ocdm.graph.graph_entity import GraphEntity 

18from oc_ocdm.decorators import accepts_only 

19from oc_ocdm.graph.entities.bibliographic_entity import BibliographicEntity 

20 

21 

22class ResourceEmbodiment(BibliographicEntity): 

23 """Resource embodiment (short: re): the particular physical or digital format in which a 

24 bibliographic resource was made available by its publisher.""" 

25 

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

27 """ 

28 The merge operation allows combining two ``ResourceEmbodiment`` entities into a single one, 

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

30 ``ResourceEmbodiment``. Moreover, every triple from the containing ``GraphSet`` referring to the second 

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

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

33 

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

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

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

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

38 

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

40 be merged into the current entity. 

41 :type other: ResourceEmbodiment 

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

43 :return: None 

44 """ 

45 super()._merge_properties(other, prefer_self) 

46 assert isinstance(other, ResourceEmbodiment) 

47 

48 media_type: Optional[URIRef] = other.get_media_type() 

49 if media_type is not None: 

50 self.has_media_type(media_type) 

51 

52 starting_page: Optional[str] = other.get_starting_page() 

53 if starting_page is not None: 

54 self.has_starting_page(starting_page) 

55 

56 ending_page: Optional[str] = other.get_ending_page() 

57 if ending_page is not None: 

58 self.has_ending_page(ending_page) 

59 

60 url: Optional[URIRef] = other.get_url() 

61 if url is not None: 

62 self.has_url(url) 

63 

64 # HAS FORMAT 

65 def get_media_type(self) -> Optional[URIRef]: 

66 """ 

67 Getter method corresponding to the ``dcterms:format`` RDF predicate. 

68 

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

70 """ 

71 uri: Optional[URIRef] = self._get_uri_reference(GraphEntity.iri_has_format) 

72 return uri 

73 

74 @accepts_only('thing') 

75 def has_media_type(self, thing_res: URIRef) -> None: 

76 """ 

77 Setter method corresponding to the ``dcterms:format`` RDF predicate. 

78 

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

80 

81 `It allows one to specify the IANA media type of the embodiment.` 

82 

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

84 :type thing_res: URIRef 

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

86 :return: None 

87 """ 

88 self.remove_media_type() 

89 self.g.add((self.res, GraphEntity.iri_has_format, thing_res)) 

90 

91 def remove_media_type(self) -> None: 

92 """ 

93 Remover method corresponding to the ``dcterms:format`` RDF predicate. 

94 

95 :return: None 

96 """ 

97 self.g.remove((self.res, GraphEntity.iri_has_format, None)) 

98 

99 # HAS FIRST PAGE 

100 def get_starting_page(self) -> Optional[str]: 

101 """ 

102 Getter method corresponding to the ``prism:startingPage`` RDF predicate. 

103 

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

105 """ 

106 return self._get_literal(GraphEntity.iri_starting_page) 

107 

108 @accepts_only('literal') 

109 def has_starting_page(self, string: str) -> None: 

110 """ 

111 Setter method corresponding to the ``prism:startingPage`` RDF predicate. 

112 

113 The string gets internally preprocessed by eventually removing dashes and everything 

114 that follows them (e.g. '22-45' becomes '22'). 

115 

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

117 

118 `The first page of the bibliographic resource according to the current embodiment.` 

119 

120 :param string: The value that will be set as the object of the property related to this method. **It must 

121 be a string that starts with an integer number.** 

122 :type string: str 

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

124 :return: None 

125 """ 

126 self.remove_starting_page() 

127 if re.search("[-–]+", string) is None: 

128 page_number = string 

129 else: 

130 page_number = re.sub("[-–]+.*$", "", string) 

131 self._create_literal(GraphEntity.iri_starting_page, page_number) 

132 

133 def remove_starting_page(self) -> None: 

134 """ 

135 Remover method corresponding to the ``prism:startingPage`` RDF predicate. 

136 

137 :return: None 

138 """ 

139 self.g.remove((self.res, GraphEntity.iri_starting_page, None)) 

140 

141 # HAS LAST PAGE 

142 def get_ending_page(self) -> Optional[str]: 

143 """ 

144 Getter method corresponding to the ``prism:endingPage`` RDF predicate. 

145 

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

147 """ 

148 return self._get_literal(GraphEntity.iri_ending_page) 

149 

150 @accepts_only('literal') 

151 def has_ending_page(self, string: str) -> None: 

152 """ 

153 Setter method corresponding to the ``prism:endingPage`` RDF predicate. 

154 

155 The string gets internally preprocessed by eventually removing dashes and everything 

156 that comes before them (e.g. '22-45' becomes '45'). 

157 

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

159 

160 `The last page of the bibliographic resource according to the current embodiment.` 

161 

162 :param string: The value that will be set as the object of the property related to this method. **It must 

163 be a string that ends with an integer number.** 

164 :type string: str 

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

166 :return: None 

167 """ 

168 self.remove_ending_page() 

169 if re.search("[-–]+", string) is None: 

170 page_number = string 

171 else: 

172 page_number = re.sub("^.*[-–]+", "", string) 

173 self._create_literal(GraphEntity.iri_ending_page, page_number) 

174 

175 def remove_ending_page(self) -> None: 

176 """ 

177 Remover method corresponding to the ``prism:endingPage`` RDF predicate. 

178 

179 :return: None 

180 """ 

181 self.g.remove((self.res, GraphEntity.iri_ending_page, None)) 

182 

183 # HAS URL 

184 def get_url(self) -> Optional[URIRef]: 

185 """ 

186 Getter method corresponding to the ``frbr:exemplar`` RDF predicate. 

187 

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

189 """ 

190 uri: Optional[URIRef] = self._get_uri_reference(GraphEntity.iri_has_url) 

191 return uri 

192 

193 @accepts_only('thing') 

194 def has_url(self, thing_res: URIRef) -> None: 

195 """ 

196 Setter method corresponding to the ``frbr:exemplar`` RDF predicate. 

197 

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

199 

200 `The URL at which the embodiment of the bibliographic resource is available.` 

201 

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

203 :type thing_res: URIRef 

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

205 :return: None 

206 """ 

207 self.remove_url() 

208 self.g.add((self.res, GraphEntity.iri_has_url, thing_res)) 

209 

210 def remove_url(self) -> None: 

211 """ 

212 Remover method corresponding to the ``frbr:exemplar`` RDF predicate. 

213 

214 :return: None 

215 """ 

216 self.g.remove((self.res, GraphEntity.iri_has_url, None)) 

217 

218 # HAS TYPE 

219 def create_digital_embodiment(self) -> None: 

220 """ 

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

222 It implicitly sets the object value ``fabio:DigitalManifestation``. 

223 

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

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

226 will be overwritten!** 

227 

228 `It identifies the particular type of the embodiment, either digital or print.` 

229 

230 :return: None 

231 """ 

232 self._create_type(GraphEntity.iri_digital_manifestation) 

233 

234 def create_print_embodiment(self) -> None: 

235 """ 

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

237 It implicitly sets the object value ``fabio:PrintObject``. 

238 

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

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

241 will be overwritten!** 

242 

243 `It identifies the particular type of the embodiment, either digital or print.` 

244 

245 :return: None 

246 """ 

247 self._create_type(GraphEntity.iri_print_object)