Skip to content

Commit

Permalink
Add SKOS-XL labels. Refs #125
Browse files Browse the repository at this point in the history
  • Loading branch information
koenedaele committed Nov 10, 2024
1 parent fda805f commit 6406c65
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 29 deletions.
5 changes: 5 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
next
----

- Allow for SKOS-XL labels by adding a URI to a Label. (#125)

1.2.0 (2022-08-11)
------------------

Expand Down
7 changes: 6 additions & 1 deletion examples/dump_jsonld.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@
'uri': 'http://id.trees.org/1',
'labels': [
{'type': 'prefLabel', 'language': 'en', 'label': 'The Larch'},
{'type': 'prefLabel', 'language': 'nl', 'label': 'De Lariks'}
{
'uri': 'http://id.trees.org/labels/lariks-nl',
'type': 'prefLabel',
'language': 'nl',
'label': 'De Lariks'
}
],
'notes': [
{'type': 'definition', 'language': 'en', 'note': 'A type of tree.'}
Expand Down
44 changes: 44 additions & 0 deletions skosprovider/jsonld.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@version": 1.1,
"dct": "http://purl.org/dc/terms/",
"skos": "http://www.w3.org/2004/02/skos/core#",
"skosxl": "http://www.w3.org/2008/05/skos-xl#",
"iso-thes": "http://purl.org/iso25964/skos-thes#",
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
Expand Down Expand Up @@ -66,6 +67,7 @@
"@type": "@id"
},
"labels": "@nest",
"labels_xl": "@nest",
"notes": "@nest",
"matches": "@nest",
"sources": {
Expand All @@ -91,6 +93,21 @@
"@id": "skos:hiddenLabel",
"@container": "@set"
},
"pref_labels_xl": {
"@nest": "labels_xl",
"@id": "skosxl:prefLabel",
"@container": "@set"
},
"alt_labels_xl": {
"@nest": "labels_xl",
"@id": "skosxl:altLabel",
"@container": "@set"
},
"hidden_labels_xl": {
"@nest": "labels_xl",
"@id": "skosxl:hiddenLabel",
"@container": "@set"
},
"general_notes": {
"@nest": "notes",
"@id": "skos:note",
Expand Down Expand Up @@ -244,6 +261,7 @@ def jsonld_c_dumper(provider, id, context = None, relations_profile =
if dataset_uri:
doc['in_dataset'] = dataset_uri
doc.update(_jsonld_labels_renderer(c))
doc.update(_jsonld_labels_xl_renderer(c))
doc.update(_jsonld_notes_renderer(c))
doc.update(_jsonld_sources_renderer(c))
doc.update(_jsonld_member_of_renderer(c, provider, relations_profile, language))
Expand Down Expand Up @@ -301,6 +319,31 @@ def lbl_renderer(l):
doc['labels'].setdefault(ltypemap[l.type], []).append(lbl_renderer(l))
return doc

def _jsonld_labels_xl_renderer(c):
doc = {
'labels_xl': {}
}
def lbl_xl_renderer(l):
language = extract_language(l.language)
return {
'uri': l.uri,
'type': 'skosxl:Label',
'skosxl:literalForm': {
'@language': language,
'lbl': l.label
}
}
ltypemap = {
'prefLabel': 'pref_labels_xl',
'altLabel': 'alt_labels_xl',
'hiddenLabel': 'hidden_labels_xl',
'sortLabel': 'hidden_labels_xl'
}
for l in c.labels:
if l.is_xl():
doc['labels_xl'].setdefault(ltypemap[l.type], []).append(lbl_xl_renderer(l))
return doc

def _jsonld_notes_renderer(c):
doc = {
'notes': {}
Expand Down Expand Up @@ -431,6 +474,7 @@ def jsonld_conceptscheme_dumper(provider, context = None,
doc['in_dataset'] = dataset_uri
doc['id'] = provider.get_metadata()['id']
doc.update(_jsonld_labels_renderer(cs))
doc.update(_jsonld_labels_xl_renderer(cs))
doc.update(_jsonld_notes_renderer(cs))
doc.update(_jsonld_sources_renderer(cs))
doc.update(_jsonld_cs_languages_renderer(cs))
Expand Down
26 changes: 21 additions & 5 deletions skosprovider/skos.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class Label:
'''
A :term:`SKOS` Label.
'''

uri = None
'''A :term:`URI` for this label.'''

label = None
'''
Expand All @@ -51,18 +54,27 @@ class Label:
The valid types for a label
'''

def __init__(self, label, type="prefLabel", language="und"):
def __init__(self, label, type="prefLabel", language="und", uri=None):
self.label = label
self.type = type
if not language:
language = 'und'
if uri and not is_uri(uri):
raise ValueError('%s is not a valid URI.' % uri)
self.uri = uri
if tags.check(language):
self.language = language
else:
raise ValueError('%s is not a valid IANA language tag.' % language)

def __eq__(self, other):
return self.__dict__ == (other if type(other) == dict else other.__dict__)
if type(other) == dict:
if self.uri:
return self.uri == other['uri']
return self.label == other['label'] and self.type == other['type'] and self.language == other['language']
if self.uri:
return self.uri == other.uri
return self.label == other.label and self.type == other.type and self.language == other.language

def __ne__(self, other):
return not self == other
Expand All @@ -76,6 +88,9 @@ def is_valid_type(type):
'''
return type in Label.valid_types

def is_xl(self):
return self.uri is not None

def __repr__(self):
return "Label('{}', '{}', '{}')".format(self.label, self.type, self.language)

Expand Down Expand Up @@ -590,8 +605,8 @@ def filter_labels_by_language(labels, language, broader=False):

def dict_to_label(dict):
'''
Transform a dict with keys `label`, `type` and `language` into a
:class:`Label`.
Transform a dict with keys `label`, `type`, `language` and `uri`
into a :class:`Label`.
Only the `label` key is mandatory. If `type` is not present, it will
default to `prefLabel`. If `language` is not present, it will default
Expand All @@ -604,7 +619,8 @@ def dict_to_label(dict):
return Label(
dict['label'],
dict.get('type', 'prefLabel'),
dict.get('language', 'und')
dict.get('language', 'und'),
uri = dict.get('uri', None)
)
except (KeyError, AttributeError, TypeError):
return dict
Expand Down
6 changes: 6 additions & 0 deletions skosprovider/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ def dict_dumper(provider):
for stuff in provider.get_all():
c = provider.get_by_id(stuff['id'])
labels = [l.__dict__ for l in c.labels]
labels = []
for l in c.labels:
ldict = l.__dict__
if 'uri' in ldict and not ldict['uri']:
del ldict['uri']
labels.append(ldict)
notes = [n.__dict__ for n in c.notes]
sources = [s.__dict__ for s in c.sources]
if isinstance(c, Concept):
Expand Down
13 changes: 13 additions & 0 deletions tests/test_jsonld.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,19 @@ def test_dump_trees_cs_url_context(self):
assert '@context' in doc
assert doc['@context'] == context_uri

def test_dump_trees_cs_label(self):
context_uri = 'https://atramhasis.org/context/atramhasis.jsonld'
doc = jsonld_conceptscheme_dumper(trees, context_uri)
assert 'labels_xl' in doc
assert 'pref_labels_xl' in doc['labels_xl']
assert len(doc['labels_xl']['pref_labels_xl']) == 1
assert doc['labels_xl']['pref_labels_xl'][0]['type'] == 'skosxl:Label'
assert doc['labels_xl']['pref_labels_xl'][0]['uri'] == 'http://id.trees.org/labels/soorten-nl'
assert doc['labels_xl']['pref_labels_xl'][0]['skosxl:literalForm'] == {
'@language': 'nl',
'lbl': 'Soorten'
}

def test_dump_trees(self):
doc = jsonld_dumper(trees)
assert '@graph' in doc
Expand Down
18 changes: 12 additions & 6 deletions tests/test_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@
'uri': 'http://id.trees.org/1',
'labels': [
{'type': 'prefLabel', 'language': 'en', 'label': 'The Larch'},
{'type': 'prefLabel', 'language': 'nl', 'label': 'De Lariks'}
{
'uri': 'http://id.trees.org/labels/lariks-nl',
'type': 'prefLabel',
'language': 'nl',
'label': 'De Lariks'
}
],
'notes': [
{'type': 'definition', 'language': 'en', 'note': 'A type of tree.', 'markup': None}
Expand Down Expand Up @@ -96,6 +101,7 @@
concept_scheme=ConceptScheme(
'http://id.trees.org',
labels = [{
'uri': 'http://id.trees.org/labels/soorten-nl',
'type': 'prefLabel',
'language': 'nl',
'label': 'Soorten'
Expand Down Expand Up @@ -343,10 +349,10 @@ def test_override_instance_scopes(self):

def test_get_by_id(self):
lariks = trees.get_by_id(1)
self.assertEqual(larch['id'], lariks.id)
self.assertEqual(larch['uri'], lariks.uri)
self.assertEqual(larch['labels'], lariks.labels)
self.assertEqual(larch['notes'], lariks.notes)
assert larch['id'] == lariks.id
assert larch['uri'] == lariks.uri
assert larch['labels'] == lariks.labels
assert larch['notes'] == lariks.notes
assert len(larch['sources']) == len(lariks.sources)
assert larch['sources'][0]['citation'] == lariks.sources[0].citation

Expand All @@ -362,7 +368,7 @@ def test_collection_has_scheme(self):

def test_get_by_uri(self):
lariks = trees.get_by_uri('http://id.trees.org/1')
self.assertEqual('http://id.trees.org/1', lariks.uri)
assert 'http://id.trees.org/1' == lariks.uri

def test_get_by_id_string(self):
lariks = trees.get_by_id('1')
Expand Down
44 changes: 33 additions & 11 deletions tests/test_skos.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,45 +26,67 @@ def tearDown(self):

def testConstructor(self):
l = Label('Knokke-Heist', type="prefLabel", language='nl-BE')
self.assertEqual('Knokke-Heist', l.label)
self.assertEqual('prefLabel', l.type)
self.assertEqual('nl-BE', l.language)
assert l.label == 'Knokke-Heist'
assert l.type == 'prefLabel'
assert l.language == 'nl-BE'
assert l.uri is None

def testConstructorInvalidLanguage(self):
with self.assertRaises(ValueError):
l = Label('Knokke-Heist', type="prefLabel", language='nederlands')
l = Label('Knokke-Heist', type='prefLabel', language=None)
assert l.language == 'und'

def testConstructorOptionalFields(self):
l = Label(
'Knokke-Heist',
type='prefLabel',
language='nl-BE',
uri='urn:x-skosprovider:gemeenten:knokke-heist:nl-BE'
)
assert l.uri == 'urn:x-skosprovider:gemeenten:knokke-heist:nl-BE'
l = Label(
'Knokke-Heist',
uri='urn:x-skosprovider:gemeenten:knokke-heist:nl-BE'
)
assert l.uri == 'urn:x-skosprovider:gemeenten:knokke-heist:nl-BE'

def testRepr(self):
l = Label('Knokke-Heist', type="prefLabel", language='nl-BE')
self.assertEqual("Label('Knokke-Heist', 'prefLabel', 'nl-BE')", l.__repr__())
assert l.__repr__() == "Label('Knokke-Heist', 'prefLabel', 'nl-BE')"

def testIsValidType(self):
self.assertTrue(Label.is_valid_type('prefLabel'))
self.assertFalse(Label.is_valid_type('voorkeursLabel'))
assert Label.is_valid_type('prefLabel')
assert not Label.is_valid_type('voorkeursLabel')
l = Label('Knokke-Heist')
self.assertTrue(l.is_valid_type('prefLabel'))
assert l.is_valid_type('prefLabel')

def testEquality(self):
l1 = Label('Knokke-Heist')
l2 = Label('Knokke-Heist', 'prefLabel', 'und')
self.assertEqual(l1, l2)
assert l1 == l2

def testInequality(self):
l1 = Label('Knokke-Heist')
l2 = Label('Knokke', 'altLabel')
self.assertNotEqual(l1, l2)
assert not l1 == l2

def testDictEquality(self):
l1 = Label('Knokke-Heist')
l2 = {'label': 'Knokke-Heist', 'type': 'prefLabel', 'language': 'und'}
self.assertEqual(l1, l2)
assert l1 == l2

def testDictInequality(self):
l1 = Label('Knokke-Heist')
l2 = {'label': 'Knokke', 'type': 'altLabel', 'language': None}
self.assertNotEqual(l1, l2)
assert not l1 == l2

def testUriEquality(self):
l1 = Label('Knokke-Heist', uri='urn:x-skosprovider:gemeenten:Knokke-Heist:nl-BE')
l2 = Label('Knokke-Heist', uri='urn:x-skosprovider:gemeenten:Knokke-Heist:nl')
l3 = Label('Cnocke-Heyst', type='altLabel', language='vls', uri='urn:x-skosprovider:gemeenten:Knokke-Heist:nl-BE')
assert not l1 == l2
assert l1 == l3


class NoteTest(unittest.TestCase):
Expand Down
14 changes: 8 additions & 6 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ def setUp(self):
'type': 'concept',
'labels': [
{'type': 'prefLabel', 'language': 'en', 'label': 'The Larch'},
{'type': 'prefLabel', 'language': 'nl', 'label': 'De Lariks'}
{
'uri': 'http://id.trees.org/labels/lariks-nl',
'type': 'prefLabel',
'language': 'nl',
'label': 'De Lariks'
}
],
'notes': [
{'type': 'definition',
Expand Down Expand Up @@ -159,13 +164,10 @@ def testEmptyProvider(self):

def testOneElementProvider(self):
pv = self._get_flat_provider([larch])
self.assertEqual([self.larch_dump], dict_dumper(pv))
assert [self.larch_dump] == dict_dumper(pv)

def testFlatProvider(self):
self.assertEqual(
[self.larch_dump, self.chestnut_dump, self.species_dump],
dict_dumper(trees)
)
assert dict_dumper(trees) == [self.larch_dump, self.chestnut_dump, self.species_dump]

def testEmptyTreeprovider(self):
pv = self._get_tree_provider([])
Expand Down

0 comments on commit 6406c65

Please sign in to comment.