pylode 2.8.3__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyLODE-2.8.3.dist-info/LICENSE +643 -0
- pyLODE-2.8.3.dist-info/METADATA +772 -0
- pyLODE-2.8.3.dist-info/RECORD +63 -0
- pyLODE-2.8.3.dist-info/WHEEL +6 -0
- pyLODE-2.8.3.dist-info/entry_points.txt +3 -0
- pyLODE-2.8.3.dist-info/top_level.txt +1 -0
- pylode/__init__.py +5 -0
- pylode/cli.py +191 -0
- pylode/common.py +191 -0
- pylode/curies.py +2208 -0
- pylode/profiles/__init__.py +4 -0
- pylode/profiles/base.py +463 -0
- pylode/profiles/ontdoc.py +1283 -0
- pylode/profiles/prof.py +276 -0
- pylode/profiles/profile.py +135 -0
- pylode/profiles/vocpub.py +605 -0
- pylode/server.py +24 -0
- pylode/style/pylode.css +179 -0
- pylode/templates/agent.html +11 -0
- pylode/templates/agent.md +11 -0
- pylode/templates/namespaces.html +9 -0
- pylode/templates/namespaces.md +5 -0
- pylode/templates/ontdoc/class.html +121 -0
- pylode/templates/ontdoc/class.md +40 -0
- pylode/templates/ontdoc/classes.html +11 -0
- pylode/templates/ontdoc/classes.md +7 -0
- pylode/templates/ontdoc/document.html +42 -0
- pylode/templates/ontdoc/document.md +16 -0
- pylode/templates/ontdoc/metadata.html +127 -0
- pylode/templates/ontdoc/metadata.md +111 -0
- pylode/templates/ontdoc/named_individual.html +65 -0
- pylode/templates/ontdoc/named_individual.md +25 -0
- pylode/templates/ontdoc/named_individuals.html +11 -0
- pylode/templates/ontdoc/named_individuals.md +7 -0
- pylode/templates/ontdoc/properties.html +65 -0
- pylode/templates/ontdoc/properties.md +45 -0
- pylode/templates/ontdoc/property.html +129 -0
- pylode/templates/ontdoc/property.md +37 -0
- pylode/templates/orcid.html +19 -0
- pylode/templates/orcid.md +1 -0
- pylode/templates/prof/prof.html +38 -0
- pylode/templates/prof/prof.md +16 -0
- pylode/templates/prof/profile.html +90 -0
- pylode/templates/prof/profile.md +85 -0
- pylode/templates/prof/resource_descriptor.html +49 -0
- pylode/templates/prof/resource_descriptor.md +21 -0
- pylode/templates/vocpub/collection.html +51 -0
- pylode/templates/vocpub/collection.md +23 -0
- pylode/templates/vocpub/collections.html +11 -0
- pylode/templates/vocpub/collections.md +7 -0
- pylode/templates/vocpub/concept.html +115 -0
- pylode/templates/vocpub/concept.md +40 -0
- pylode/templates/vocpub/concept_hierarchy.html +80 -0
- pylode/templates/vocpub/concept_scheme.html +105 -0
- pylode/templates/vocpub/concept_scheme.md +102 -0
- pylode/templates/vocpub/concepts.html +9 -0
- pylode/templates/vocpub/concepts.md +7 -0
- pylode/templates/vocpub/taxonomy.html +42 -0
- pylode/templates/vocpub/taxonomy.md +17 -0
- pylode/tests/__init__.py +0 -0
- pylode/tests/test_ont_html_description.py +7 -0
- pylode/tests/test_skos.py +8 -0
- pylode/tests/tests_all.py +50 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
from pylode.common import VERSION, STYLE_DIR, TEMPLATES_DIR
|
|
2
|
+
import collections
|
|
3
|
+
from os import path
|
|
4
|
+
from rdflib import URIRef, BNode, Literal
|
|
5
|
+
from rdflib.namespace import DC, DCTERMS, DOAP, OWL, PROV, RDF, RDFS, SDO, SKOS
|
|
6
|
+
import dateutil.parser
|
|
7
|
+
from itertools import chain
|
|
8
|
+
import markdown
|
|
9
|
+
from jinja2 import Environment, FileSystemLoader
|
|
10
|
+
from os.path import join
|
|
11
|
+
from pylode.profiles.base import BaseProfile
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class VocPub(BaseProfile):
|
|
15
|
+
def __init__(self, g, source_info, outputformat="html", exclude_css=False, default_language="en", get_curies_online=False):
|
|
16
|
+
super().__init__(g, source_info, outputformat=outputformat, exclude_css=exclude_css, get_curies_online=False, default_language=default_language)
|
|
17
|
+
self.CONCEPTS = collections.OrderedDict()
|
|
18
|
+
self.COLLECTIONS = collections.OrderedDict()
|
|
19
|
+
|
|
20
|
+
def _load_template(self, template_file):
|
|
21
|
+
return Environment(loader=FileSystemLoader(join(TEMPLATES_DIR, "vocpub"))).get_template(template_file)
|
|
22
|
+
|
|
23
|
+
def _make_formatted_uri(self, uri, type=None):
|
|
24
|
+
# set display to CURIE
|
|
25
|
+
short = self._get_curie(uri)
|
|
26
|
+
# if the URI base is within the default namespace of this ontology
|
|
27
|
+
# use the fragment URI
|
|
28
|
+
# else
|
|
29
|
+
# use the given URI
|
|
30
|
+
uri_base = self._get_namespace_from_uri(uri)
|
|
31
|
+
link = None
|
|
32
|
+
if uri_base == self.METADATA.get("default_namespace"):
|
|
33
|
+
if self.CONCEPTS.get(uri):
|
|
34
|
+
link = "[{}]({})".format(self.CONCEPTS[uri]["default_prefLabel"], self.CONCEPTS[uri]["fid"]) \
|
|
35
|
+
if self.outputformat == "md" \
|
|
36
|
+
else '<a href="#{}">{}</a>'.format(self.CONCEPTS[uri]["fid"], self.CONCEPTS[uri]["default_prefLabel"])
|
|
37
|
+
elif self.COLLECTIONS.get(uri):
|
|
38
|
+
link = "[{}]({})".format(self.COLLECTIONS[uri]["default_prefLabel"], self.COLLECTIONS[uri]["fid"]) \
|
|
39
|
+
if self.outputformat == "md" \
|
|
40
|
+
else '<a href="#{}">{}</a>'.format(self.COLLECTIONS[uri]["fid"], self.COLLECTIONS[uri]["default_prefLabel"])
|
|
41
|
+
|
|
42
|
+
if link is None:
|
|
43
|
+
link = "[{}]({})".format(short, uri) \
|
|
44
|
+
if self.outputformat == "md" \
|
|
45
|
+
else '<a href="{}">{}</a>'.format(uri, short)
|
|
46
|
+
|
|
47
|
+
if type == "cp":
|
|
48
|
+
if self.outputformat == "md":
|
|
49
|
+
suffix = ' (cp)'
|
|
50
|
+
else:
|
|
51
|
+
suffix = '<sup class="sup-cp" title="concept">cp</sup>'
|
|
52
|
+
elif type == "cl":
|
|
53
|
+
if self.outputformat == "md":
|
|
54
|
+
suffix = ' (cl)'
|
|
55
|
+
else:
|
|
56
|
+
suffix = '<sup class="sup-cl" title="collection">cl</sup>'
|
|
57
|
+
# None
|
|
58
|
+
else:
|
|
59
|
+
suffix = ''
|
|
60
|
+
|
|
61
|
+
return link + suffix
|
|
62
|
+
|
|
63
|
+
def _expand_graph(self):
|
|
64
|
+
# name
|
|
65
|
+
for s, o in chain(
|
|
66
|
+
self.G.subject_objects(DC.title),
|
|
67
|
+
self.G.subject_objects(RDFS.label),
|
|
68
|
+
self.G.subject_objects(DCTERMS.title)
|
|
69
|
+
):
|
|
70
|
+
self.G.add((s, SKOS.prefLabel, o))
|
|
71
|
+
|
|
72
|
+
# description
|
|
73
|
+
for s, o in chain(
|
|
74
|
+
self.G.subject_objects(DC.description),
|
|
75
|
+
self.G.subject_objects(DCTERMS.description),
|
|
76
|
+
self.G.subject_objects(RDFS.comment),
|
|
77
|
+
self.G.subject_objects(SDO.description)
|
|
78
|
+
):
|
|
79
|
+
self.G.add((s, SKOS.definition, o))
|
|
80
|
+
|
|
81
|
+
# OWL -> SKOS
|
|
82
|
+
# classes as Concepts types
|
|
83
|
+
for s in chain(
|
|
84
|
+
self.G.subjects(RDF.type, RDFS.Class),
|
|
85
|
+
self.G.subjects(RDF.type, OWL.Class)
|
|
86
|
+
):
|
|
87
|
+
self.G.add((s, RDF.type, SKOS.Concept))
|
|
88
|
+
|
|
89
|
+
# SKOS Concept Hierarchy from Class subsumption
|
|
90
|
+
for s, o in self.G.subject_objects(RDFS.subClassOf):
|
|
91
|
+
if type(o) != BNode: # stops restrictions being seen as broader/narrower
|
|
92
|
+
self.G.add((s, SKOS.broader, o))
|
|
93
|
+
self.G.add((o, SKOS.narrower, s))
|
|
94
|
+
|
|
95
|
+
for s, o in self.G.subject_objects(OWL.equivalentClass):
|
|
96
|
+
self.G.add((s, SKOS.exactMatch, o))
|
|
97
|
+
self.G.add((o, SKOS.exactMatch, s))
|
|
98
|
+
|
|
99
|
+
# the ontology is now a ConceptScheme
|
|
100
|
+
for s in self.G.subjects(RDF.type, OWL.Ontology):
|
|
101
|
+
self.G.add((s, RDF.type, SKOS.ConceptScheme))
|
|
102
|
+
|
|
103
|
+
# top concepts
|
|
104
|
+
# if the class (now typed a Concept) is declared here and
|
|
105
|
+
# has no subClassOf
|
|
106
|
+
# or is subClassOf skos:Concept
|
|
107
|
+
# or is only a subClassOf BNodes (restrictions)
|
|
108
|
+
for s2 in self.G.subjects(RDF.type, SKOS.Concept):
|
|
109
|
+
if (s2, RDFS.subClassOf, None) not in self.G:
|
|
110
|
+
self.G.add((s2, SKOS.topConceptOf, s))
|
|
111
|
+
|
|
112
|
+
only_bn = True
|
|
113
|
+
for o3 in self.G.objects(s2, RDFS.subClassOf):
|
|
114
|
+
if type(o3) != BNode:
|
|
115
|
+
only_bn = False
|
|
116
|
+
if only_bn:
|
|
117
|
+
self.G.add((s2, SKOS.topConceptOf, s))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# SKOS -> SKOS
|
|
121
|
+
# broader / narrower buildout
|
|
122
|
+
for s, o in self.G.subject_objects(SKOS.broader):
|
|
123
|
+
self.G.add((o, SKOS.narrower, s))
|
|
124
|
+
|
|
125
|
+
for s, o in self.G.subject_objects(SKOS.narrower):
|
|
126
|
+
self.G.add((o, SKOS.broader, s))
|
|
127
|
+
|
|
128
|
+
for s, o in self.G.subject_objects(SKOS.topConceptOf):
|
|
129
|
+
self.G.add((o, SKOS.hasTopConcept, s))
|
|
130
|
+
|
|
131
|
+
for s, o in self.G.subject_objects(SKOS.hasTopConcept):
|
|
132
|
+
self.G.add((o, SKOS.topConceptOf, s))
|
|
133
|
+
|
|
134
|
+
# Agents
|
|
135
|
+
# creator
|
|
136
|
+
for s, o in chain(
|
|
137
|
+
self.G.subject_objects(DC.creator),
|
|
138
|
+
self.G.subject_objects(SDO.creator),
|
|
139
|
+
self.G.subject_objects(SDO.author) # conflate SDO.author with DCTERMS.creator
|
|
140
|
+
):
|
|
141
|
+
self.G.remove((s, DC.creator, o))
|
|
142
|
+
self.G.remove((s, SDO.creator, o))
|
|
143
|
+
self.G.remove((s, SDO.author, o))
|
|
144
|
+
self.G.add((s, DCTERMS.creator, o))
|
|
145
|
+
|
|
146
|
+
# contributor
|
|
147
|
+
for s, o in chain(
|
|
148
|
+
self.G.subject_objects(DC.contributor),
|
|
149
|
+
self.G.subject_objects(SDO.contributor)
|
|
150
|
+
):
|
|
151
|
+
self.G.remove((s, DC.contributor, o))
|
|
152
|
+
self.G.remove((s, SDO.contributor, o))
|
|
153
|
+
self.G.add((s, DCTERMS.contributor, o))
|
|
154
|
+
|
|
155
|
+
# publisher
|
|
156
|
+
for s, o in chain(
|
|
157
|
+
self.G.subject_objects(DC.publisher),
|
|
158
|
+
self.G.subject_objects(SDO.publisher)
|
|
159
|
+
):
|
|
160
|
+
self.G.remove((s, DC.publisher, o))
|
|
161
|
+
self.G.remove((s, SDO.publisher, o))
|
|
162
|
+
self.G.add((s, DCTERMS.publisher, o))
|
|
163
|
+
|
|
164
|
+
def _extract_collections(self):
|
|
165
|
+
"""Extracts standard SKOS Collection metadata"""
|
|
166
|
+
collections = []
|
|
167
|
+
# TODO: handle OrderedCollections
|
|
168
|
+
for s in self.G.subjects(predicate=RDF.type, object=SKOS.Collection):
|
|
169
|
+
collections.append(str(s))
|
|
170
|
+
|
|
171
|
+
# keeping the OrderedDict ordered
|
|
172
|
+
for c in sorted(collections):
|
|
173
|
+
self.COLLECTIONS[c] = {}
|
|
174
|
+
|
|
175
|
+
# fill in each Collection's details from the graph
|
|
176
|
+
for c in self.COLLECTIONS.keys():
|
|
177
|
+
s = URIRef(c) # for use in Graph() loops
|
|
178
|
+
|
|
179
|
+
self.COLLECTIONS[c]["fid"] = None
|
|
180
|
+
self.COLLECTIONS[c]["default_prefLabel"] = None
|
|
181
|
+
self.COLLECTIONS[c]["prefLabels"] = set()
|
|
182
|
+
self.COLLECTIONS[c]["altLabels"] = set()
|
|
183
|
+
self.COLLECTIONS[c]["definitions"] = set()
|
|
184
|
+
self.COLLECTIONS[c]["scopeNotes"] = set()
|
|
185
|
+
self.COLLECTIONS[c]["source"] = None
|
|
186
|
+
self.COLLECTIONS[c]["members"] = set()
|
|
187
|
+
|
|
188
|
+
for p, o in self.G.predicate_objects(subject=s):
|
|
189
|
+
if p == SKOS.prefLabel:
|
|
190
|
+
self.COLLECTIONS[c]["prefLabels"].add((str(o), o.language)) # TODO: add in language
|
|
191
|
+
if o.language == self.default_language:
|
|
192
|
+
self.COLLECTIONS[c]["default_prefLabel"] = str(o)
|
|
193
|
+
|
|
194
|
+
elif p == SKOS.altLabel:
|
|
195
|
+
self.COLLECTIONS[c]["altLabels"].add(str(o)) # TODO: add in language
|
|
196
|
+
|
|
197
|
+
elif p == SKOS.definition:
|
|
198
|
+
self.COLLECTIONS[c]["definitions"].add(str(o)) # TODO: add in language
|
|
199
|
+
|
|
200
|
+
elif p == SKOS.scopeNote:
|
|
201
|
+
self.COLLECTIONS[c]["scopeNotes"].add(str(o)) # TODO: add in language
|
|
202
|
+
|
|
203
|
+
elif p == DCTERMS.source:
|
|
204
|
+
self.COLLECTIONS[c]["source"] = str(o)
|
|
205
|
+
|
|
206
|
+
elif p == SKOS.topConceptOf:
|
|
207
|
+
self.COLLECTIONS[c]["topConceptOfs"].add(str(o))
|
|
208
|
+
|
|
209
|
+
elif p == SKOS.member:
|
|
210
|
+
self.COLLECTIONS[c]["members"].add(str(o))
|
|
211
|
+
# TODO: handle members that are other Collections, not Concepts
|
|
212
|
+
|
|
213
|
+
# listify the sets
|
|
214
|
+
self.COLLECTIONS[c]["prefLabels"] = list(self.COLLECTIONS[c]["prefLabels"])
|
|
215
|
+
self.COLLECTIONS[c]["altLabels"] = list(self.COLLECTIONS[c]["altLabels"])
|
|
216
|
+
self.COLLECTIONS[c]["definitions"] = list(self.COLLECTIONS[c]["definitions"])
|
|
217
|
+
self.COLLECTIONS[c]["scopeNotes"] = list(self.COLLECTIONS[c]["scopeNotes"])
|
|
218
|
+
self.COLLECTIONS[c]["members"] = list(self.COLLECTIONS[c]["members"])
|
|
219
|
+
|
|
220
|
+
# make fid
|
|
221
|
+
# TODO: update to use default language label, not [0]
|
|
222
|
+
try:
|
|
223
|
+
pl = self.COLLECTIONS[c]["prefLabels"][0][0]
|
|
224
|
+
|
|
225
|
+
self.COLLECTIONS[c]["fid"] = self._make_fid(
|
|
226
|
+
pl, c
|
|
227
|
+
)
|
|
228
|
+
except Exception as e:
|
|
229
|
+
print(e)
|
|
230
|
+
raise Exception("You Collection {} doesn't have a label but it needs one!".format(c))
|
|
231
|
+
|
|
232
|
+
def _extract_concepts(self):
|
|
233
|
+
"""Extracts standard SKOS Concepts and their metadata
|
|
234
|
+
|
|
235
|
+
Will interpret an owl:Class and rdfs:Class instances as skos:Concepts if run against an OWL document
|
|
236
|
+
"""
|
|
237
|
+
concepts = []
|
|
238
|
+
for s in self.G.subjects(predicate=RDF.type, object=SKOS.Concept):
|
|
239
|
+
concepts.append(str(s))
|
|
240
|
+
|
|
241
|
+
# keeping the OrderedDict ordered
|
|
242
|
+
for c in sorted(concepts):
|
|
243
|
+
self.CONCEPTS[c] = {}
|
|
244
|
+
|
|
245
|
+
# fill in each Concept's details from the graph
|
|
246
|
+
for c in self.CONCEPTS.keys():
|
|
247
|
+
s = URIRef(c) # for use in Graph() loops
|
|
248
|
+
|
|
249
|
+
self.CONCEPTS[c]["fid"] = None
|
|
250
|
+
self.CONCEPTS[c]["prefLabels"] = set()
|
|
251
|
+
self.CONCEPTS[c]["default_prefLabel"] = None
|
|
252
|
+
self.CONCEPTS[c]["altLabels"] = set()
|
|
253
|
+
self.CONCEPTS[c]["definitions"] = set()
|
|
254
|
+
self.CONCEPTS[c]["scopeNotes"] = set()
|
|
255
|
+
self.CONCEPTS[c]["examples"] = set()
|
|
256
|
+
self.CONCEPTS[c]["inSchemes"] = set()
|
|
257
|
+
self.CONCEPTS[c]["topConceptOfs"] = set()
|
|
258
|
+
self.CONCEPTS[c]["source"] = None
|
|
259
|
+
self.CONCEPTS[c]["broaders"] = set()
|
|
260
|
+
self.CONCEPTS[c]["narrowers"] = set()
|
|
261
|
+
self.CONCEPTS[c]["exactMatches"] = set()
|
|
262
|
+
self.CONCEPTS[c]["closeMatches"] = set()
|
|
263
|
+
self.CONCEPTS[c]["broadMatches"] = set()
|
|
264
|
+
self.CONCEPTS[c]["narrowMatches"] = set()
|
|
265
|
+
|
|
266
|
+
for p, o in self.G.predicate_objects(subject=s):
|
|
267
|
+
if p == SKOS.prefLabel:
|
|
268
|
+
self.CONCEPTS[c]["prefLabels"].add((str(o), o.language)) # TODO: add in language
|
|
269
|
+
if o.language == self.default_language:
|
|
270
|
+
self.CONCEPTS[c]["default_prefLabel"] = str(o)
|
|
271
|
+
|
|
272
|
+
elif p == SKOS.altLabel:
|
|
273
|
+
self.CONCEPTS[c]["altLabels"].add(str(o)) # TODO: add in language
|
|
274
|
+
|
|
275
|
+
elif p == SKOS.definition:
|
|
276
|
+
self.CONCEPTS[c]["definitions"].add(str(o)) # TODO: add in language
|
|
277
|
+
|
|
278
|
+
elif p == SKOS.scopeNote:
|
|
279
|
+
self.CONCEPTS[c]["scopeNotes"].add(str(o)) # TODO: add in language
|
|
280
|
+
|
|
281
|
+
elif p == SKOS.example:
|
|
282
|
+
self.CONCEPTS[c]["examples"].add(str(o)) # TODO: add in language
|
|
283
|
+
|
|
284
|
+
elif p == SKOS.inScheme:
|
|
285
|
+
self.CONCEPTS[c]["inSchemes"].add(str(o))
|
|
286
|
+
|
|
287
|
+
elif p == DCTERMS.source:
|
|
288
|
+
self.CONCEPTS[c]["source"] = str(o)
|
|
289
|
+
|
|
290
|
+
elif p == SKOS.topConceptOf:
|
|
291
|
+
self.CONCEPTS[c]["topConceptOfs"].add(str(o))
|
|
292
|
+
|
|
293
|
+
elif p == SKOS.broader:
|
|
294
|
+
self.CONCEPTS[c]["broaders"].add(str(o))
|
|
295
|
+
|
|
296
|
+
elif p == SKOS.narrower:
|
|
297
|
+
self.CONCEPTS[c]["narrowers"].add(str(o))
|
|
298
|
+
|
|
299
|
+
elif p == SKOS.exactMatch:
|
|
300
|
+
self.CONCEPTS[c]["exactMatches"].add(str(o))
|
|
301
|
+
|
|
302
|
+
elif p == SKOS.closeMatch:
|
|
303
|
+
self.CONCEPTS[c]["closeMatches"].add(str(o))
|
|
304
|
+
|
|
305
|
+
elif p == SKOS.broadMatch:
|
|
306
|
+
self.CONCEPTS[c]["broadMatches"].add(str(o))
|
|
307
|
+
|
|
308
|
+
elif p == SKOS.narrowMatch:
|
|
309
|
+
self.CONCEPTS[c]["narrowMatches"].add(str(o))
|
|
310
|
+
|
|
311
|
+
# patch title from URI if we haven't got one
|
|
312
|
+
if len(self.CONCEPTS[c]["prefLabels"]) < 1:
|
|
313
|
+
pl = self._make_title_from_uri(c)
|
|
314
|
+
self.CONCEPTS[c]["prefLabels"].add((pl, 'en'))
|
|
315
|
+
self.CONCEPTS[c]["default_prefLabel"] = pl
|
|
316
|
+
|
|
317
|
+
# listify the sets
|
|
318
|
+
self.CONCEPTS[c]["prefLabels"] = list(self.CONCEPTS[c]["prefLabels"])
|
|
319
|
+
self.CONCEPTS[c]["altLabels"] = list(self.CONCEPTS[c]["altLabels"])
|
|
320
|
+
self.CONCEPTS[c]["definitions"] = list(self.CONCEPTS[c]["definitions"])
|
|
321
|
+
self.CONCEPTS[c]["scopeNotes"] = list(self.CONCEPTS[c]["scopeNotes"])
|
|
322
|
+
self.CONCEPTS[c]["examples"] = list(self.CONCEPTS[c]["examples"])
|
|
323
|
+
self.CONCEPTS[c]["inSchemes"] = list(self.CONCEPTS[c]["inSchemes"])
|
|
324
|
+
self.CONCEPTS[c]["topConceptOfs"] = list(self.CONCEPTS[c]["topConceptOfs"])
|
|
325
|
+
self.CONCEPTS[c]["broaders"] = list(self.CONCEPTS[c]["broaders"])
|
|
326
|
+
self.CONCEPTS[c]["narrowers"] = list(self.CONCEPTS[c]["narrowers"])
|
|
327
|
+
self.CONCEPTS[c]["exactMatches"] = list(self.CONCEPTS[c]["exactMatches"])
|
|
328
|
+
self.CONCEPTS[c]["closeMatches"] = list(self.CONCEPTS[c]["closeMatches"])
|
|
329
|
+
self.CONCEPTS[c]["broadMatches"] = list(self.CONCEPTS[c]["broadMatches"])
|
|
330
|
+
self.CONCEPTS[c]["narrowMatches"] = list(self.CONCEPTS[c]["narrowMatches"])
|
|
331
|
+
|
|
332
|
+
# make fid
|
|
333
|
+
# TODO: update to use default language label, not [0]
|
|
334
|
+
self.CONCEPTS[c]["fid"] = self._make_fid(
|
|
335
|
+
self.CONCEPTS[c]["default_prefLabel"], c
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
def _extract_concept_scheme(self):
|
|
339
|
+
"""Extracts standard SKOS ConceptScheme metadata
|
|
340
|
+
|
|
341
|
+
Will interpret an owl:Ontology as a skos:ConceptScheme if run against an OWL document
|
|
342
|
+
"""
|
|
343
|
+
|
|
344
|
+
self.METADATA["creators"] = set()
|
|
345
|
+
self.METADATA["contributors"] = set()
|
|
346
|
+
self.METADATA["publishers"] = set()
|
|
347
|
+
for s in self.G.subjects(predicate=RDF.type, object=SKOS.ConceptScheme):
|
|
348
|
+
self.METADATA["uri"] = str(s)
|
|
349
|
+
for p, o in self.G.predicate_objects(subject=s):
|
|
350
|
+
if p == SKOS.prefLabel:
|
|
351
|
+
self.METADATA["title"] = str(o)
|
|
352
|
+
|
|
353
|
+
if p == SKOS.definition:
|
|
354
|
+
self.METADATA["description"] = markdown.markdown(str(o))
|
|
355
|
+
|
|
356
|
+
if p == SKOS.historyNote:
|
|
357
|
+
self.METADATA["historyNote"] = markdown.markdown(str(o))
|
|
358
|
+
|
|
359
|
+
# dates
|
|
360
|
+
if p in [DCTERMS.created, DCTERMS.modified, DCTERMS.issued]:
|
|
361
|
+
date_type = p.split("/")[-1]
|
|
362
|
+
self.METADATA[date_type] = dateutil.parser.parse(str(o)).strftime(
|
|
363
|
+
"%Y-%m-%d"
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
if p == DCTERMS.source:
|
|
367
|
+
if str(o).startswith('http'):
|
|
368
|
+
self.METADATA["source"] = self._make_formatted_uri(o) # '<a href="{0}">{0}</a>'.format(str(o))
|
|
369
|
+
else:
|
|
370
|
+
self.METADATA["source"] = str(o)
|
|
371
|
+
|
|
372
|
+
if p == OWL.versionIRI:
|
|
373
|
+
self.METADATA["versionIRI"] = self._make_formatted_uri(o)
|
|
374
|
+
|
|
375
|
+
if p == OWL.versionInfo:
|
|
376
|
+
self.METADATA["versionInfo"] = str(o)
|
|
377
|
+
|
|
378
|
+
if p == URIRef("http://purl.org/vocab/vann/preferredNamespacePrefix"):
|
|
379
|
+
self.METADATA["preferredNamespacePrefix"] = str(o)
|
|
380
|
+
|
|
381
|
+
if p == URIRef("http://purl.org/vocab/vann/preferredNamespaceUri"):
|
|
382
|
+
self.METADATA["preferredNamespaceUri"] = str(o)
|
|
383
|
+
|
|
384
|
+
if p == DCTERMS.license:
|
|
385
|
+
self.METADATA["license"] = (
|
|
386
|
+
self._make_formatted_uri(o)
|
|
387
|
+
if str(o).startswith("http")
|
|
388
|
+
else str(o)
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
if p == DCTERMS.rights:
|
|
392
|
+
self.METADATA["rights"] = (
|
|
393
|
+
str(o)
|
|
394
|
+
.replace("Copyright", "©")
|
|
395
|
+
.replace("copyright", "©")
|
|
396
|
+
.replace("(c)", "©")
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# Agents
|
|
400
|
+
if p in [DCTERMS.creator, DCTERMS.contributor, DCTERMS.publisher]:
|
|
401
|
+
agent_type = p.split("/")[-1] + "s"
|
|
402
|
+
if type(o) == Literal:
|
|
403
|
+
self.METADATA[agent_type].add(str(o))
|
|
404
|
+
else: # Blank Node or URI
|
|
405
|
+
self.METADATA[agent_type].add(self._make_agent_html(o))
|
|
406
|
+
|
|
407
|
+
# TODO: cater for other Agent representations
|
|
408
|
+
|
|
409
|
+
if p == PROV.wasGeneratedBy:
|
|
410
|
+
for o2 in self.G.objects(subject=o, predicate=DOAP.repository):
|
|
411
|
+
self.METADATA["repository"] = self._make_formatted_uri(o2)
|
|
412
|
+
|
|
413
|
+
if p == SDO.codeRepository:
|
|
414
|
+
self.METADATA["repository"] = self._make_formatted_uri(o)
|
|
415
|
+
|
|
416
|
+
if self.METADATA.get("title") is None:
|
|
417
|
+
raise ValueError(
|
|
418
|
+
"Your taxonomy's ConceptScheme does not indicate any form of title. "
|
|
419
|
+
"You must declare one of the following for it: rdfs:label, dct:title, skos:prefLabel"
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
if len(self.COLLECTIONS.keys()) > 0:
|
|
423
|
+
self.METADATA["has_collections"] = True
|
|
424
|
+
|
|
425
|
+
if len(self.CONCEPTS.keys()) > 0:
|
|
426
|
+
self.METADATA["has_concepts"] = True
|
|
427
|
+
|
|
428
|
+
def _make_skos_concept_scheme(self):
|
|
429
|
+
return self._load_template("concept_scheme." + self.outputformat).render(
|
|
430
|
+
title=self.METADATA.get("title"),
|
|
431
|
+
uri=self.METADATA.get("uri"),
|
|
432
|
+
version_uri=self.METADATA.get("versionIRI"),
|
|
433
|
+
publishers=sorted(self.METADATA["publishers"]),
|
|
434
|
+
creators=sorted(self.METADATA["creators"]),
|
|
435
|
+
contributors=sorted(self.METADATA["contributors"]),
|
|
436
|
+
created=self.METADATA.get("created"),
|
|
437
|
+
modified=self.METADATA.get("modified"),
|
|
438
|
+
issued=self.METADATA.get("issued"),
|
|
439
|
+
source=self.METADATA.get("source"),
|
|
440
|
+
description=self.METADATA.get("description"),
|
|
441
|
+
historyNote=self.METADATA.get("historyNote"),
|
|
442
|
+
version_info=self.METADATA.get("versionInfo"),
|
|
443
|
+
license=self.METADATA.get("license"),
|
|
444
|
+
rights=self.METADATA.get("rights"),
|
|
445
|
+
repository=self.METADATA.get("repository"),
|
|
446
|
+
ont_rdf=self._make_source_file_link(),
|
|
447
|
+
has_collections=True if len(self.COLLECTIONS) > 0 else False,
|
|
448
|
+
has_concepts=True if len(self.CONCEPTS) > 0 else False,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
def _make_skos_collection(self, collection):
|
|
452
|
+
return self._load_template("collection." + self.outputformat).render(
|
|
453
|
+
uri=collection[0],
|
|
454
|
+
fid=collection[1].get("fid"),
|
|
455
|
+
default_prefLabel=collection[1].get("default_prefLabel"),
|
|
456
|
+
prefLabels=collection[1].get("prefLabels"),
|
|
457
|
+
altLabels=collection[1].get("altLabels"),
|
|
458
|
+
definitions=collection[1].get("definitions"),
|
|
459
|
+
scopeNotes=collection[1].get("scopeNotes"),
|
|
460
|
+
source=collection[1].get("source"),
|
|
461
|
+
members=[self._make_formatted_uri(x, type="cp") for x in collection[1].get("members")],
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
def _make_skos_collections(self):
|
|
465
|
+
collections = []
|
|
466
|
+
for k, v in self.COLLECTIONS.items():
|
|
467
|
+
collections.append(
|
|
468
|
+
(
|
|
469
|
+
v["default_prefLabel"],
|
|
470
|
+
v["fid"],
|
|
471
|
+
self._make_skos_collection((k, v)),
|
|
472
|
+
)
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
return self._load_template("collections." + self.outputformat).render(collections=collections)
|
|
476
|
+
|
|
477
|
+
def _make_concept_hierarchy(self):
|
|
478
|
+
of = self.outputformat
|
|
479
|
+
# render concept
|
|
480
|
+
def _render(c, children, of, level=0):
|
|
481
|
+
if of == "md":
|
|
482
|
+
md = level*"\t" + "* [{}]({})\n".format(self.CONCEPTS.get(c).get("default_prefLabel"), c)
|
|
483
|
+
if len(children) > 0:
|
|
484
|
+
for ch in sorted(children):
|
|
485
|
+
md += _render(ch, self.CONCEPTS.get(ch).get("narrowers"), of, level=level + 1)
|
|
486
|
+
return md
|
|
487
|
+
else: # HTML
|
|
488
|
+
html = "<li>{}".format(self._make_formatted_uri(c))
|
|
489
|
+
if len(children) > 0:
|
|
490
|
+
for ch in sorted(children):
|
|
491
|
+
html += "\n<ul>" + \
|
|
492
|
+
_render(ch, self.CONCEPTS.get(ch).get("narrowers"), of, level=level + 1) + \
|
|
493
|
+
"</ul>"
|
|
494
|
+
html += "</li>"
|
|
495
|
+
return html
|
|
496
|
+
|
|
497
|
+
# start with a topConcept
|
|
498
|
+
tcs = []
|
|
499
|
+
for s, o in self.G.subject_objects(predicate=SKOS.hasTopConcept):
|
|
500
|
+
tcs.append(str(o))
|
|
501
|
+
|
|
502
|
+
txt = ""
|
|
503
|
+
for tc in sorted(tcs):
|
|
504
|
+
txt += _render(tc, self.CONCEPTS.get(tc).get("narrowers"), self.outputformat, 0)
|
|
505
|
+
|
|
506
|
+
if self.outputformat == "md":
|
|
507
|
+
return txt
|
|
508
|
+
else:
|
|
509
|
+
return "<ul class=\"hierarchy\">\n" + txt + "\n</ul>"
|
|
510
|
+
|
|
511
|
+
def _make_skos_concept(self, concept):
|
|
512
|
+
# handling Markdown formatting within a table
|
|
513
|
+
if self.outputformat == "md":
|
|
514
|
+
if len(concept[1].get("definitions")) > 0:
|
|
515
|
+
defs = [d.replace("\n", " ") for d in concept[1].get("definitions")]
|
|
516
|
+
else:
|
|
517
|
+
defs = None
|
|
518
|
+
if len(concept[1].get("examples")) > 0:
|
|
519
|
+
egs = [eg.strip().replace("\t", " ") for eg in concept[1].get("examples")]
|
|
520
|
+
egs2 = []
|
|
521
|
+
for ee in egs:
|
|
522
|
+
ee2 = ""
|
|
523
|
+
for line in ee.split("\n"):
|
|
524
|
+
ee2 += "`" + line + "`<br />"
|
|
525
|
+
egs2.append(ee2)
|
|
526
|
+
egs = egs2
|
|
527
|
+
else:
|
|
528
|
+
egs = []
|
|
529
|
+
else:
|
|
530
|
+
defs = concept[1].get("definitions")
|
|
531
|
+
if len(concept[1].get("examples")) > 1:
|
|
532
|
+
egs = [x.replace("<", "<").replace(">", ">") for x in concept[1].get("examples")]
|
|
533
|
+
else:
|
|
534
|
+
egs = []
|
|
535
|
+
|
|
536
|
+
return self._load_template("concept." + self.outputformat).render(
|
|
537
|
+
uri=concept[0],
|
|
538
|
+
fid=concept[1].get("fid"),
|
|
539
|
+
default_prefLabel=concept[1].get("default_prefLabel"),
|
|
540
|
+
prefLabels=concept[1].get("prefLabels"),
|
|
541
|
+
altLabels=concept[1].get("altLabels"),
|
|
542
|
+
definitions=defs,
|
|
543
|
+
scopeNotes=concept[1].get("scopeNotes"),
|
|
544
|
+
examples=egs,
|
|
545
|
+
source=concept[1].get("source"),
|
|
546
|
+
broaders=[self._make_formatted_uri(x, type="cp") for x in concept[1].get("broaders")],
|
|
547
|
+
narrowers=[self._make_formatted_uri(x, type="cp") for x in concept[1].get("narrowers")],
|
|
548
|
+
exactMatches=[self._make_formatted_uri(x, type="cp") for x in concept[1].get("exactMatches")],
|
|
549
|
+
closeMatches=[self._make_formatted_uri(x, type="cp") for x in concept[1].get("closeMatches")],
|
|
550
|
+
broadMatches=[self._make_formatted_uri(x, type="cp") for x in concept[1].get("broadMatches")],
|
|
551
|
+
narrowMatches=[self._make_formatted_uri(x, type="cp") for x in concept[1].get("narrowMatches")],
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
def _make_skos_concepts(self):
|
|
555
|
+
# TODO: make Concept hierarchy (URIs)
|
|
556
|
+
|
|
557
|
+
# TODO: list all Concepts, alphabetically by prefLabel
|
|
558
|
+
concepts = []
|
|
559
|
+
for k, v in self.CONCEPTS.items():
|
|
560
|
+
concepts.append(
|
|
561
|
+
(
|
|
562
|
+
v["default_prefLabel"],
|
|
563
|
+
v["fid"],
|
|
564
|
+
self._make_skos_concept((k, v)),
|
|
565
|
+
)
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
return self._load_template("concepts." + self.outputformat).render(
|
|
569
|
+
concept_hierarchy=self._make_concept_hierarchy(),
|
|
570
|
+
concepts=concepts
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
def _make_document(self):
|
|
574
|
+
css = None
|
|
575
|
+
if self.outputformat == "html":
|
|
576
|
+
if not self.exclude_css:
|
|
577
|
+
css = open(path.join(STYLE_DIR, "pylode.css")).read()
|
|
578
|
+
|
|
579
|
+
return self._load_template("taxonomy." + self.outputformat).render(
|
|
580
|
+
schemaorg=self._make_schemaorg_metadata(), # only does something for the HTML template
|
|
581
|
+
title=self.METADATA["title"],
|
|
582
|
+
concept_scheme=self._make_skos_concept_scheme(),
|
|
583
|
+
has_collections=True if len(self.COLLECTIONS) > 0 else False,
|
|
584
|
+
collections=self._make_skos_collections(),
|
|
585
|
+
has_concepts=True if len(self.CONCEPTS) > 0 else False,
|
|
586
|
+
concepts=self._make_skos_concepts(),
|
|
587
|
+
namespaces=self._make_namespaces(),
|
|
588
|
+
css=css,
|
|
589
|
+
pylode_version=VERSION
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
def generate_document(self):
|
|
593
|
+
# expand the graph using pre-defined rules to make querying easier (poor man's inference)
|
|
594
|
+
self._expand_graph()
|
|
595
|
+
# get all the namespaces using several methods
|
|
596
|
+
self._extract_namespaces()
|
|
597
|
+
# get the default namespace
|
|
598
|
+
self._get_default_namespace()
|
|
599
|
+
# extract all the SKOS things
|
|
600
|
+
self._extract_collections()
|
|
601
|
+
self._extract_concepts()
|
|
602
|
+
self._make_concept_hierarchy()
|
|
603
|
+
self._extract_concept_scheme()
|
|
604
|
+
|
|
605
|
+
return self._make_document()
|
pylode/server.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import falcon
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DocResource:
|
|
6
|
+
def on_get(self, req, resp):
|
|
7
|
+
"""Handles GET requests"""
|
|
8
|
+
url = req.get_param("url")
|
|
9
|
+
if url is not None:
|
|
10
|
+
cmd = "cd ./bin && ./pylode.sh -u {url} -c true".format(url=url)
|
|
11
|
+
|
|
12
|
+
# remove Overview image placeholder, if present
|
|
13
|
+
raw_html = subprocess.check_output(cmd, shell=True)
|
|
14
|
+
processed_html = raw_html.decode().replace(
|
|
15
|
+
'<section id="overview">', '<section id="overview" style="display:none;">').encode()
|
|
16
|
+
resp.body = processed_html
|
|
17
|
+
|
|
18
|
+
resp.set_header("Powered-By", "Falcon")
|
|
19
|
+
resp.set_header("content-type", "text/html")
|
|
20
|
+
resp.status = falcon.HTTP_200
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
api = falcon.API()
|
|
24
|
+
api.add_route("/lode", DocResource())
|