toolbox.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. import logging
  2. import pickle
  3. import re
  4. from collections import defaultdict
  5. from hashlib import sha1
  6. from flask import g
  7. from rdflib import Graph
  8. from rdflib.term import URIRef, Variable
  9. from lakesuperior.dictionaries.namespaces import ns_collection as nsc
  10. from lakesuperior.globals import ROOT_RSRC_URI
  11. logger = logging.getLogger(__name__)
  12. class Toolbox:
  13. '''
  14. Utility class to translate and generate strings and other objects.
  15. '''
  16. def replace_term_domain(self, term, search, replace):
  17. '''
  18. Replace the domain of a term.
  19. @param term (URIRef) The term (URI) to change.
  20. @param search (string) Domain string to replace.
  21. @param replace (string) Domain string to use for replacement.
  22. @return URIRef
  23. '''
  24. s = str(term)
  25. if s.startswith(search):
  26. s = s.replace(search, replace)
  27. return URIRef(s)
  28. def uid_to_uri(self, uid):
  29. '''Convert a UID to a URI.
  30. @return URIRef
  31. '''
  32. return URIRef(g.webroot + uid)
  33. def uri_to_uid(self, uri):
  34. '''Convert an absolute URI (internal or external) to a UID.
  35. @return string
  36. '''
  37. if uri.startswith(nsc['fcres']):
  38. return str(uri).replace(nsc['fcres'], '')
  39. else:
  40. return '/' + str(uri).replace(g.webroot, '').strip('/')
  41. def localize_uri_string(self, s):
  42. '''Convert URIs into URNs in a string using the application base URI.
  43. @param string s Input string.
  44. @return string
  45. '''
  46. if s.strip('/') == g.webroot:
  47. return str(ROOT_RSRC_URI)
  48. else:
  49. return s.rstrip('/').replace(
  50. g.webroot, str(nsc['fcres']))
  51. def localize_term(self, uri):
  52. '''
  53. Localize an individual term.
  54. @param rdflib.term.URIRef urn Input URI.
  55. @return rdflib.term.URIRef
  56. '''
  57. return URIRef(self.localize_uri_string(str(uri)))
  58. def localize_triple(self, trp):
  59. '''
  60. Localize terms in a triple.
  61. @param trp (tuple(rdflib.term.URIRef)) The triple to be converted
  62. @return tuple(rdflib.term.URIRef)
  63. '''
  64. s, p, o = trp
  65. if s.startswith(g.webroot):
  66. s = self.localize_term(s)
  67. if o.startswith(g.webroot):
  68. o = self.localize_term(o)
  69. return s, p, o
  70. def localize_graph(self, gr):
  71. '''
  72. Localize a graph.
  73. '''
  74. l_gr = Graph()
  75. for trp in gr:
  76. l_gr.add(self.localize_triple(trp))
  77. return l_gr
  78. def localize_payload(self, data):
  79. '''
  80. Localize an RDF stream with domain-specific URIs.
  81. @param data (bytes) Binary RDF data.
  82. @return bytes
  83. '''
  84. return data.replace(
  85. (g.webroot + '/').encode('utf-8'),
  86. (nsc['fcres'] + '/').encode('utf-8')
  87. ).replace(
  88. g.webroot.encode('utf-8'),
  89. (nsc['fcres'] + '/').encode('utf-8')
  90. )
  91. def localize_ext_str(self, s, urn):
  92. '''
  93. Convert global URIs to local in a SPARQL or RDF string.
  94. Also replace empty URIs (`<>`) with a fixed local URN and take care
  95. of fragments and relative URIs.
  96. This is a 3-pass replacement. First, global URIs whose webroot matches
  97. the application ones are replaced with internal URIs. Then, relative
  98. URIs are converted to absolute using the internal URI as the base;
  99. finally, the root node is appropriately addressed.
  100. '''
  101. esc_webroot = g.webroot.replace('/', '\\/')
  102. #loc_ptn = r'<({}\/?)?(.*?)?(\?.*?)?(#.*?)?>'.format(esc_webroot)
  103. loc_ptn1 = r'<{}\/?(.*?)>'.format(esc_webroot)
  104. loc_sub1 = '<{}/\\1>'.format(nsc['fcres'])
  105. s1 = re.sub(loc_ptn1, loc_sub1, s)
  106. loc_ptn2 = r'<([#?].*?)?>'
  107. loc_sub2 = '<{}\\1>'.format(urn)
  108. s2 = re.sub(loc_ptn2, loc_sub2, s1)
  109. loc_ptn3 = r'<{}([#?].*?)?>'.format(nsc['fcres'])
  110. loc_sub3 = '<{}\\1>'.format(ROOT_RSRC_URI)
  111. s3 = re.sub(loc_ptn3, loc_sub3, s2)
  112. return s3
  113. def globalize_string(self, s):
  114. '''Convert URNs into URIs in a string using the application base URI.
  115. @param string s Input string.
  116. @return string
  117. '''
  118. return s.replace(str(nsc['fcres']), g.webroot)
  119. def globalize_term(self, urn):
  120. '''
  121. Convert an URN into an URI using the application base URI.
  122. @param rdflib.term.URIRef urn Input URN.
  123. @return rdflib.term.URIRef
  124. '''
  125. return URIRef(self.globalize_string(str(urn)))
  126. def globalize_triple(self, trp):
  127. '''
  128. Globalize terms in a triple.
  129. @param trp (tuple(rdflib.term.URIRef)) The triple to be converted
  130. @return tuple(rdflib.term.URIRef)
  131. '''
  132. s, p, o = trp
  133. if s.startswith(nsc['fcres']):
  134. s = self.globalize_term(s)
  135. if o.startswith(nsc['fcres']):
  136. o = self.globalize_term(o)
  137. return s, p, o
  138. def globalize_graph(self, gr):
  139. '''
  140. Globalize a graph.
  141. '''
  142. g_gr = Graph()
  143. for trp in gr:
  144. g_gr.add(self.globalize_triple(trp))
  145. return g_gr
  146. def globalize_rsrc(self, rsrc):
  147. '''
  148. Globalize a resource.
  149. '''
  150. gr = rsrc.graph
  151. urn = rsrc.identifier
  152. global_gr = self.globalize_graph(gr)
  153. global_uri = self.globalize_term(urn)
  154. return global_gr.resource(global_uri)
  155. def parse_rfc7240(self, h_str):
  156. '''
  157. Parse `Prefer` header as per https://tools.ietf.org/html/rfc7240
  158. The `cgi.parse_header` standard method does not work with all possible
  159. use cases for this header.
  160. @param h_str (string) The header(s) as a comma-separated list of Prefer
  161. statements, excluding the `Prefer: ` token.
  162. '''
  163. parsed_hdr = defaultdict(dict)
  164. # Split up headers by comma
  165. hdr_list = [ x.strip() for x in h_str.split(',') ]
  166. for hdr in hdr_list:
  167. parsed_pref = defaultdict(dict)
  168. # Split up tokens by semicolon
  169. token_list = [ token.strip() for token in hdr.split(';') ]
  170. prefer_token = token_list.pop(0).split('=')
  171. prefer_name = prefer_token[0]
  172. # If preference has a '=', it has a value, else none.
  173. if len(prefer_token)>1:
  174. parsed_pref['value'] = prefer_token[1].strip('"')
  175. for param_token in token_list:
  176. # If the token list had a ';' the preference has a parameter.
  177. param_parts = [ prm.strip().strip('"') \
  178. for prm in param_token.split('=') ]
  179. param_value = param_parts[1] if len(param_parts) > 1 else None
  180. parsed_pref['parameters'][param_parts[0]] = param_value
  181. parsed_hdr[prefer_name] = parsed_pref
  182. return parsed_hdr
  183. def rdf_cksum(self, gr):
  184. '''
  185. Generate a checksum for a graph.
  186. What this method does is ordering the graph by subject, predicate,
  187. object, then creating a pickle string and a checksum of it.
  188. N.B. The context of the triples is ignored, so isomorphic graphs would
  189. have the same checksum regardless of the context(s) they are found in.
  190. @TODO This can be later reworked to use a custom hashing algorithm.
  191. @param rdflib.Graph gr The graph to be hashed.
  192. @return string SHA1 checksum.
  193. '''
  194. # Remove the messageDigest property, which very likely reflects the
  195. # previous state of the resource.
  196. gr.remove((Variable('s'), nsc['premis'].messageDigest, Variable('o')))
  197. ord_gr = sorted(list(gr), key=lambda x : (x[0], x[1], x[2]))
  198. hash = sha1(pickle.dumps(ord_gr)).hexdigest()
  199. return hash
  200. def split_uuid(self, uuid):
  201. '''
  202. Split a UID into pairtree segments. This mimics FCREPO4 behavior.
  203. '''
  204. path = '{}/{}/{}/{}/{}'.format(uuid[:2], uuid[2:4],
  205. uuid[4:6], uuid[6:8], uuid)
  206. return path