├── .gitignore ├── LICENSE.txt ├── Makefile ├── README.md ├── bibdb_datasets.py ├── common.py ├── datasets.py ├── docs.py ├── examples ├── context.jsonld ├── mappings │ ├── bibframe-identifiers.ttl │ ├── libris-uri-scheme.ttl │ ├── misc-skos-mappings.ttl │ ├── rda-bf2-types.ttl │ └── services.ttl └── queries │ ├── select-wikidata-languages.rq │ ├── select_labels.rq │ ├── select_missing_domainrange.rq │ └── select_missing_domainrange_datatypeprop.rq ├── lxltools ├── __init__.py ├── contextmaker.py ├── datacompiler.py ├── ldutil.py ├── lxlslug.py └── timeutil.py ├── query.py ├── requirements.txt ├── scripts ├── analyze-enums.py ├── checkdisplay.py ├── construct.py ├── create_i18n_datasets.py ├── create_sab_skos_data.py ├── create_ssif_science_topic_data.py ├── digest-marcframe.py ├── extract-marcframe-terms.py ├── extract_sab_data_from_docx.py ├── get-model-terms-from-example-usage.py ├── get-proxy-domains-ranges.rq ├── load-defs-whelk.sh ├── make-lexical-rda.py ├── make-lexical-rda.rq ├── make_rda_mappings.py ├── map-mads-from-skos.rq ├── marc │ ├── README.md │ ├── describe_marcmap.py │ ├── frbrize_marcmap.py │ ├── marcmap-tokenmaps.json │ ├── marcmap.json │ ├── parse_legacy_config.py │ └── vocab_from_marcmap.py ├── marcframe-skeleton-from-bibframe.py ├── marcframe-skeleton-from-marcmap.py ├── misc │ ├── convert-marcxml-to-bibframe.sh │ ├── jsontr.py │ ├── mk-graph-data.py │ └── vocab-summary.py ├── vocab-from-marcframe.py ├── vocab-update-cleanup.rq └── vocab-update-unstable.rq ├── source ├── a11y-terms.ttl ├── app │ └── help.jsonld ├── apps.jsonld ├── bibdb │ └── terms.ttl ├── bibliographies.jsonld ├── cc-licenses-header.txt ├── cc-licenses.ttl ├── changecategories.ttl ├── construct-cc-license-data.rq ├── construct-countries.rq ├── construct-languages-iso639-1.rq ├── construct-languages-iso639-2.rq ├── construct-libraries.rq ├── construct-materials.rq ├── construct-musnotationsterms.rq ├── construct-rda-terms.rq ├── construct-relators.rq ├── construct-tacnotationterms.rq ├── containers.ttl ├── countries.ttl ├── datasets │ ├── bibdb.ttl │ ├── external.ttl │ ├── i18n.ttl │ ├── idkbse.ttl │ ├── sab.ttl │ ├── signe.ttl │ └── syscore.ttl ├── doc │ ├── about.sv.mkd │ ├── issues.en.mkd │ ├── issues │ │ ├── concept-thing.en.mkd │ │ ├── content-carrier.en.mkd │ │ ├── literal-structures.en.mkd │ │ └── record-thing.en.mkd │ ├── model.en.mkd │ ├── philosophy.en.mkd │ ├── services.en.mkd │ ├── standards.en.mkd │ └── summary.sv.mkd ├── encodingFormat-terms.ttl ├── generators.ttl ├── i18n │ ├── collections.ttl │ ├── rules.ttl │ ├── scripts.ttl │ └── tlangs.ttl ├── identifiers.ttl ├── kbv-enums.ttl ├── languages.ttl ├── marc │ ├── base.ttl │ ├── construct-enums.rq │ ├── enums.ttl │ ├── indicators.ttl │ ├── terms.ttl │ └── unlabelled.ttl ├── materials.ttl ├── movementnotation.ttl ├── musicnotation.ttl ├── nationalities.ttl ├── policies.ttl ├── rda-terms.ttl ├── relators.ttl ├── remote │ ├── construct-aat-materials.rq │ ├── construct-bnf-roles.rq │ └── construct-matching-wd-labels.rq ├── repr-terms.ttl ├── sab.ttl ├── schemes.ttl ├── ssif-2025-kbv.jsonld ├── ssif-2025-skos.ttl ├── swepub │ ├── output-types.ttl │ ├── publication-types.ttl │ ├── terms.ttl │ ├── types.ttl │ ├── update.rq │ └── vocab.ttl ├── tactilenotation.ttl ├── update-enums.rq └── vocab │ ├── accessibility.ttl │ ├── agents.ttl │ ├── base.ttl │ ├── bf-map.ttl │ ├── bf-to-kbv-base.rq │ ├── check-bases.rq │ ├── concepts.ttl │ ├── construct-enum-restrictions.rq │ ├── details.ttl │ ├── display.jsonld │ ├── enums.ttl │ ├── external-labels.ttl │ ├── files-packages-representations.ttl │ ├── items.ttl │ ├── libris-search-experimental.ttl │ ├── platform.ttl │ ├── relations.ttl │ ├── things.ttl │ ├── unstable.ttl │ ├── update.rq │ └── updateResource.rq ├── sys └── context │ ├── base.jsonld │ ├── kbv.jsonld │ ├── keywords.jsonld │ ├── ns.jsonld │ ├── shared.jsonld │ └── target │ ├── bibo-w3c.jsonld │ ├── loc-w3c-sdo.jsonld │ └── sdo-w3c.jsonld └── syscore.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Cached Data and Packages 2 | /cache 3 | /viewer/node_modules 4 | /viewer/bower_components 5 | 6 | # Generated Files 7 | /build 8 | /viewer/static/build 9 | /viewer/tests/bin 10 | /viewer/static/vendor 11 | /logs 12 | 13 | # Local App Config 14 | /.venv 15 | /venv 16 | .python-version 17 | 18 | # Common OS, Tool and Editor Files 19 | .*.sw* 20 | .DS_Store 21 | npm-debug.log 22 | *.pyc 23 | .idea/ 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | source/cc-licenses.ttl: source/construct-cc-license-data.rq 2 | cat source/cc-licenses-header.txt > $@ 3 | (echo https://creativecommons.org/publicdomain/mark/1.0/ && echo https://creativecommons.org/publicdomain/mark/1.0/deed.sv && curl -sL https://creativecommons.org/about/cclicenses/ | sed -nE 's!.*> $@ 4 | 5 | source/sab.ttl: scripts/extract_sab_data_from_docx.py cache/esab-2015_1.docx 6 | python3 $^ | trld -ijsonld -ottl > $@ 7 | # TODO 1: enhance with DDC-mappings: scripts/create_sab_skos_data.py + 8 | # ../librisxl/whelk-core/src/main/java/se/kb/libris/export/dewey/dewey_sab.txt 9 | # TODO 2: In XL, add precomposed usages (extract from usage in records)? See: 10 | # ../librisxl/marc_export/src/main/resources/se/kb/libris/export/sabrub.txt # precomposed 11 | 12 | ## SSIF 2011 (Obsolete) 13 | # 14 | #cache/ssif.xlsx: 15 | # curl https://www.uka.se/download/18.11258e6a184d17a7b2c2e5/1669982839699/Forskningsamnen-standard-2011.xlsx -o cache/ssif.xlsx 16 | # 17 | # See: . 18 | # * 9 = Tab 19 | # * 90 = UTF-8 20 | #cache/ssif.csv: cache/ssif.xlsx 21 | # libreoffice --headless --convert-to csv:"Text - txt - csv (StarCalc)":"9,ANSI,90" $^ --outdir cache/ 22 | # 23 | #source/ssif.jsonld: scripts/create_ssif_science_topic_data.py cache/ssif.csv 24 | # python3 $^ > $@ 25 | # 26 | #source/ssif.ttl: source/ssif.jsonld 27 | # trld $^ -o ttl > $@ 28 | 29 | ## SSIF 2025 30 | 31 | # - External source form (to be published at/via uka.se) 32 | source/ssif-2025-skos.ttl: cache/ssif-2025-skos.jsonld 33 | trld $^ -o ttl > $@ 34 | 35 | cache/ssif-2025-skos.jsonld: scripts/create_ssif_science_topic_data.py cache/ssif-2025.csv 36 | python3 $^ --skos > $@ 37 | 38 | # - Internal target form (to be fetched from source, mapped using TVM and cached in XL) 39 | source/ssif-2025-kbv.jsonld: scripts/create_ssif_science_topic_data.py cache/ssif-2025.csv 40 | python3 $^ > $@ 41 | 42 | # ...,2 = Sheet 2 - see 43 | cache/ssif-2025.csv: cache/Nyckel_SSIF2011_SSIF2025_digg.xlsx 44 | libreoffice --headless --convert-to csv:"Text - txt - csv (StarCalc)":"9,ANSI,90,,,true,true,false,false,,,2" cache/Nyckel_SSIF2011_SSIF2025_digg.xlsx --outdir cache/ 45 | # Fix incomplete change type 46 | sed -i '/^60411.*\tBytt benämning\t/ s/Bytt benämning/Ny kod, Bytt benämning/' "cache/Nyckel_SSIF2011_SSIF2025_digg-Nyckel SSIF2011-SSIF25.csv" 47 | cp "cache/Nyckel_SSIF2011_SSIF2025_digg-Nyckel SSIF2011-SSIF25.csv" $@ 48 | -------------------------------------------------------------------------------- /bibdb_datasets.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from lxltools.datacompiler import Compiler 3 | import os 4 | 5 | 6 | SCRIPT_DIR = os.path.dirname(__file__) or '.' 7 | BASE = 'https://libris.kb.se/' 8 | 9 | 10 | compiler = Compiler(base_dir=SCRIPT_DIR, 11 | dataset_id=BASE + 'dataset/bibdb', 12 | context='sys/context/base.jsonld', 13 | record_thing_link='mainEntity', 14 | system_base_iri="", 15 | union='libraries.jsonld.lines', 16 | created="2019-03-14T15:00:00.000Z") 17 | 18 | 19 | @compiler.dataset 20 | def libraries(): 21 | graph = _construct_bibdb_data('sigel=*&holdings=True&org_type=library') 22 | return "/library", "2019-03-14T15:31:17.000Z", graph 23 | 24 | 25 | @compiler.dataset 26 | def bibliographies(): 27 | graph = _construct_bibdb_data('sigel=*&org_type=bibliography') 28 | return "/library", "2019-03-14T19:32:20.000Z", graph 29 | 30 | 31 | def _construct_bibdb_data(query): 32 | libraries = _fetch_libraries(f'https://bibdb.libris.kb.se/api?{query}') 33 | bidb_context = 'https://bibdb.libris.kb.se/libdb/static/meta/context.jsonld' 34 | return compiler.construct(sources=[ 35 | { 36 | "dataset": BASE + "dataset/libraries", 37 | "data": libraries, 38 | "context": [ 39 | compiler.load_json(compiler.cache_url(bidb_context)), 40 | { 41 | "@base": "http://bibdb.libris.kb.se/", 42 | # TODO: these are dropped in the source context; fix that and drop this 43 | "date_created": "http://libris.kb.se/def/lib#date_created", 44 | "date_modified": "http://libris.kb.se/def/lib#date_modified", 45 | } 46 | ] 47 | } 48 | ], 49 | query="source/construct-libraries.rq") 50 | 51 | 52 | def _fetch_libraries(start_url): 53 | url = start_url 54 | result = [] 55 | 56 | start = 0 57 | batch = 200 58 | while True: 59 | data = compiler.load_json(compiler.cache_url(url)) 60 | libraries = data['libraries'] 61 | if libraries: 62 | result += libraries 63 | start += batch 64 | url = f'{start_url}&start={start}' 65 | else: 66 | break 67 | 68 | return result 69 | 70 | 71 | if __name__ == '__main__': 72 | compiler.main() 73 | -------------------------------------------------------------------------------- /common.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from lxltools.datacompiler import Compiler 4 | 5 | compiler = Compiler( 6 | base_dir=os.path.dirname(__file__), 7 | datasets_description="source/datasets/idkbse.ttl", 8 | dataset_id="https://id.kb.se/dataset/common", 9 | created="2013-10-17T14:07:48.000Z", 10 | tool_id="https://id.kb.se/generator/datasetcompiler", 11 | context="sys/context/base.jsonld", 12 | system_base_iri="", 13 | union="common.jsonld.lines", 14 | ) 15 | 16 | if __name__ == "__main__": 17 | compiler.main() 18 | -------------------------------------------------------------------------------- /datasets.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | from lxltools.datacompiler import Compiler 5 | import syscore 6 | import common 7 | import docs 8 | 9 | SCRIPT_DIR = os.path.dirname(__file__) or '.' 10 | BASE = "https://id.kb.se/" 11 | 12 | compiler = Compiler(base_dir=SCRIPT_DIR, 13 | dataset_id=BASE + 'dataset/definitions', 14 | created='2013-10-17T14:07:48.000Z', 15 | tool_id=BASE + 'generator/definitions', 16 | context='sys/context/base.jsonld', 17 | record_thing_link='mainEntity', 18 | system_base_iri='', 19 | union='definitions.jsonld.lines') 20 | 21 | if __name__ == '__main__': 22 | compiler.main() 23 | syscore.compiler.main() 24 | common.compiler.main() 25 | docs.compiler.main() 26 | 27 | out_dir = compiler.outdir 28 | with (out_dir / compiler.union).open('ab') as defs_f: 29 | for mod in [syscore, common, docs]: 30 | with (out_dir / mod.compiler.union).open('rb') as f: 31 | shutil.copyfileobj(f, defs_f) 32 | -------------------------------------------------------------------------------- /docs.py: -------------------------------------------------------------------------------- 1 | import os 2 | import markdown 3 | from lxltools.datacompiler import Compiler 4 | 5 | BASE = "https://id.kb.se/" 6 | 7 | SCRIPT_DIR = os.path.dirname(__file__) or '.' 8 | 9 | compiler = Compiler(base_dir=SCRIPT_DIR, 10 | dataset_id=BASE + 'dataset/docs', 11 | created='2016-04-15T14:42:00.000Z', 12 | tool_id=BASE + 'generator/docsbuilder', 13 | context='sys/context/base.jsonld', 14 | record_thing_link='mainEntity', 15 | system_base_iri='', 16 | union='docs.jsonld.lines') 17 | 18 | 19 | @compiler.dataset 20 | def id_docs(): 21 | docs = [] 22 | sourcepath = compiler.path('source') 23 | for fpath in (sourcepath / 'doc').glob('**/*.mkd'): 24 | text = fpath.read_text('utf-8') 25 | html = markdown.markdown(text) 26 | doc_id = (str(fpath.relative_to(sourcepath)) 27 | .replace(os.sep, '/') 28 | .replace('.mkd', '')) 29 | doc_id, dot, lang = doc_id.partition('.') 30 | doc = { 31 | "@type": "Article", 32 | "@id": BASE + doc_id, 33 | "articleBody": html 34 | } 35 | h1end = html.find('') 36 | if h1end > -1: 37 | doc['title'] = html[len('

'):h1end] 38 | if lang: 39 | doc['language'] = {"langTag": lang}, 40 | docs.append(doc) 41 | 42 | return "/doc", "2016-04-15T14:43:38.072Z", { 43 | "@context": "../sys/context/base.jsonld", 44 | "@graph": docs 45 | } 46 | 47 | 48 | if __name__ == '__main__': 49 | compiler.main() 50 | -------------------------------------------------------------------------------- /examples/context.jsonld: -------------------------------------------------------------------------------- 1 | { 2 | "@context": [ 3 | { 4 | "@vocab": "http://schema.org/", 5 | "dc": "http://purl.org/dc/terms/", 6 | "rdael": "http://rdvocab.info/Elements/", 7 | "title": {"@id": "dc:title", "@language": "sv"}, 8 | "responsibilityStatement": {"@id": "rdael:statementOfResponsibility", "@language": "sv"}, 9 | "description": {"@language": "sv"}, 10 | "dateCreated": {"@type": "Date"}, 11 | "datePublished": {"@type": "Date"}, 12 | "startDate": {"@type": "Date"}, 13 | "endDate": {"@type": "Date"} 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/mappings/bibframe-identifiers.ttl: -------------------------------------------------------------------------------- 1 | @prefix rdfs: . 2 | @prefix owl: . 3 | @prefix : . 4 | @prefix ext: . 5 | 6 | 7 | :isbn a owl:DatatypeProperty; 8 | owl:propertyChainAxiom ( 9 | [ a owl:ObjectProperty; 10 | rdfs:subPropertyOf :identifier; 11 | rdfs:range [ a owl:Restriction; 12 | owl:onProperty :identifierScheme; owl:hasValue "ISBN" ] ] 13 | :identifierValue ) . 14 | 15 | :invalidIsbn a owl:DatatypeProperty; 16 | owl:propertyChainAxiom ( 17 | [ a owl:ObjectProperty; 18 | rdfs:subPropertyOf :identifier; 19 | rdfs:range [ a owl:Restriction; 20 | owl:onProperty :identifierScheme; owl:hasValue "ISBN" ], 21 | [ a owl:Restriction; 22 | owl:onProperty :identifierStatus; owl:hasValue "invalid" ] ] 23 | :identifierValue ) . 24 | 25 | 26 | ext:agentLink a owl:ObjectProperty; 27 | owl:inverseOf rdf:subject; 28 | owl:range :AgentLink . 29 | 30 | ext:AgentLink a owl:Class; 31 | owl:subClassOf rdf:Statement, [ 32 | a owl:Restriction; owl:onProperty ext:relator; owl:hasValue :associatedAgent 33 | ] . 34 | 35 | ext:relator a owl:ObjectProperty; 36 | rdfs:subPropertyOf rdf:predicate; 37 | owl:domain ext:AgentLink . 38 | 39 | ext:agent a owl:ObjectProperty; 40 | rdfs:subPropertyOf rdf:object; 41 | owl:domain ext:AgentLink . 42 | 43 | -------------------------------------------------------------------------------- /examples/mappings/libris-uri-scheme.ttl: -------------------------------------------------------------------------------- 1 | @prefix coin: . 2 | @prefix foaf: . 3 | @prefix bf: . 4 | @base . 5 | 6 | a coin:URISpace; 7 | coin:base "http://libris.kb.se"; 8 | coin:apply coin:ToLowerCase; 9 | coin:spaceReplacement "-"; 10 | coin:template [ 11 | coin:forType foaf:Document; 12 | coin:uriTemplate "/bib/{controlNumber}" 13 | ], [ 14 | coin:forType bf:Instance; 15 | coin:uriTemplate "/resource/bib/{recordControlNumber}" 16 | ], [ 17 | coin:forType bf:Work; 18 | coin:relFromBase bf:instanceOf; 19 | coin:uriTemplate "{+base}#work"; 20 | ] . 21 | 22 | # /bib/ 23 | # /auth/ 24 | # /resource/bib/ 25 | # /resource/auth/ 26 | # /resource/library/{sigel} # unused 27 | # /work/1 28 | 29 | @prefix xsd: . 30 | @prefix dct: . 31 | @prefix void: . 32 | @prefix skos: . 33 | 34 | a void:Dataset; 35 | coin:slug "person"^^xsd:anyURI; 36 | dct:created "2013-02-11T11:01:00Z"^^xsd:dateTime . 37 | 38 | -------------------------------------------------------------------------------- /examples/mappings/misc-skos-mappings.ttl: -------------------------------------------------------------------------------- 1 | @prefix : . 2 | @prefix sab: . 3 | @prefix bicssc: . 4 | 5 | sab:Lz a :Concept; 6 | :notation "Lz"; 7 | :exactMatch . 8 | 9 | bicssc:FH a :Concept; 10 | :notation "FH"; 11 | :exactMatch . 12 | 13 | bicssc:FK a :Concept; 14 | :notation "FK"; 15 | :exactMatch . 16 | 17 | bicssc:FL a :Concept; 18 | :notation "FL"; 19 | :exactMatch . 20 | 21 | bicssc:FM a :Concept; 22 | :notation "FM"; 23 | :exactMatch . 24 | 25 | -------------------------------------------------------------------------------- /examples/mappings/services.ttl: -------------------------------------------------------------------------------- 1 | @prefix : . 2 | @prefix hydra: . 3 | 4 | :Library hydra:search [ 5 | hydra:template "https://bibdb.libris.kb.se/api/library" ; 6 | hydra:mapping [ hydra:property :sigel; hydra:variable "sigel" ] 7 | ] . 8 | -------------------------------------------------------------------------------- /examples/queries/select-wikidata-languages.rq: -------------------------------------------------------------------------------- 1 | ## 2 | # Example invocation: 3 | # $ curl -Haccept:text/csv -s https://query.wikidata.org/sparql --data-urlencode "query=$(cat $THIS_FILE)" 4 | 5 | prefix rdfs: 6 | prefix wd: 7 | prefix wdt: 8 | 9 | select ?id ?code ?label { 10 | ?id wdt:P31 wd:Q34770 ; 11 | wdt:P220 ?code ; 12 | rdfs:label ?label . 13 | } 14 | -------------------------------------------------------------------------------- /examples/queries/select_labels.rq: -------------------------------------------------------------------------------- 1 | prefix rdfs: 2 | 3 | select * { 4 | ?s rdfs:label ?label . 5 | } 6 | 7 | -------------------------------------------------------------------------------- /examples/queries/select_missing_domainrange.rq: -------------------------------------------------------------------------------- 1 | prefix rdfs: 2 | prefix owl: 3 | prefix sdo: 4 | 5 | select distinct ?s ?domain ?range { 6 | { 7 | ?s a owl:DatatypeProperty 8 | } union { 9 | ?s a owl:ObjectProperty 10 | } 11 | optional { 12 | ?s (rdfs:domain|sdo:domainIncludes) ?domain . 13 | } 14 | optional { 15 | ?s (rdfs:range|sdo:rangeIncludes) ?range . 16 | } 17 | filter(!bound(?domain) || !bound(?range)) 18 | } 19 | 20 | -------------------------------------------------------------------------------- /examples/queries/select_missing_domainrange_datatypeprop.rq: -------------------------------------------------------------------------------- 1 | prefix rdfs: 2 | prefix owl: 3 | prefix sdo: 4 | 5 | select distinct ?s ?domain ?range { 6 | { 7 | ?s a owl:DatatypeProperty 8 | optional { 9 | ?s (rdfs:domain|sdo:domainIncludes) ?domain . 10 | } 11 | filter(!bound(?domain)) 12 | } 13 | union 14 | { 15 | ?s a owl:ObjectProperty 16 | optional { 17 | ?s (rdfs:domain|sdo:domainIncludes) ?domain . 18 | } 19 | optional { 20 | ?s (rdfs:range|sdo:rangeIncludes) ?range . 21 | } 22 | filter(!bound(?domain) || !bound(?range)) 23 | } 24 | } 25 | order by asc(?s) 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /lxltools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libris/definitions/6bdd1fd1eb739ebd5817215a7605b77c4cf1fafd/lxltools/__init__.py -------------------------------------------------------------------------------- /lxltools/ldutil.py: -------------------------------------------------------------------------------- 1 | from rdflib import Graph 2 | from rdflib.plugins.serializers.jsonld import from_rdf 3 | from rdflib.plugins.parsers.jsonld import to_rdf 4 | 5 | 6 | def to_jsonld(source: Graph, context_uri: str, contextobj) -> dict: 7 | data = from_rdf(source, context_data=contextobj) 8 | data['@context'] = context_uri 9 | _embed_singly_referenced_bnodes(data) 10 | _expand_ids(data['@graph'], contextobj['@context']) 11 | return data 12 | 13 | 14 | def _expand_ids(obj, pfx_map): 15 | """ 16 | Ensure @id values are in expanded form (i.e. full URIs). 17 | """ 18 | if isinstance(obj, list): 19 | for item in obj: 20 | _expand_ids(item, pfx_map) 21 | elif isinstance(obj, dict): 22 | node_id = obj.get('@id') 23 | if node_id: 24 | pfx, colon, leaf = node_id.partition(':') 25 | ns = pfx_map.get(pfx) 26 | if ns: 27 | obj['@id'] = node_id.replace(pfx + ':', ns, 1) 28 | for value in obj.values(): 29 | _expand_ids(value, pfx_map) 30 | 31 | 32 | def _embed_singly_referenced_bnodes(data): 33 | graph_index = {item['@id']: item for item in data.pop('@graph')} 34 | bnode_refs = {} 35 | 36 | def collect_refs(node): 37 | for values in node.values(): 38 | if not isinstance(values, list): 39 | values = [values] 40 | for value in values: 41 | if isinstance(value, dict): 42 | if value.get('@id', '').startswith('_:'): 43 | bnode_refs.setdefault(value['@id'], []).append(value) 44 | collect_refs(value) 45 | 46 | for node in graph_index.values(): 47 | collect_refs(node) 48 | 49 | for refid, refs in bnode_refs.items(): 50 | if len(refs) == 1: 51 | refs[0].update(graph_index.pop(refid)) 52 | refs[0].pop('@id') 53 | 54 | data['@graph'] = sorted(graph_index.values(), key=lambda node: node['@id']) 55 | -------------------------------------------------------------------------------- /lxltools/lxlslug.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import unicode_literals, print_function 3 | 4 | from zlib import crc32 5 | import string 6 | import time 7 | import random 8 | 9 | 10 | lower_consonants_numbers = string.digits + "".join( 11 | c for c in string.ascii_lowercase if c not in "aoueiy") 12 | 13 | def tobase(alphabet, i): 14 | v = '' 15 | size = len(alphabet) 16 | n = i 17 | while True: 18 | n, rest = divmod(n, size) 19 | v = alphabet[rest] + v 20 | if not n: 21 | return v 22 | 23 | def caesarize(alphabet, s): 24 | size = len(alphabet) 25 | last = s[-1] 26 | rotation = alphabet.index(last) 27 | def rotate(c): 28 | pos = alphabet.index(c) + rotation 29 | return pos - size if pos >= size else pos 30 | return "".join(alphabet[rotate(c)] for c in s[:-1]) + last 31 | 32 | def checksum(data): 33 | return crc32(data.encode('utf-8')) & 0xffffffff 34 | 35 | def librisencode(a, b): 36 | alphabet = lower_consonants_numbers 37 | timepart = "".join(reversed(caesarize(alphabet, tobase(alphabet, a)))) 38 | codepart = tobase(alphabet, b) 39 | codelen = len(codepart) 40 | if codelen < 7: 41 | codepart = ("0" * (7 - codelen)) + codepart 42 | return timepart + codepart 43 | 44 | 45 | if __name__ == '__main__': 46 | import sys 47 | import os.path as P 48 | 49 | args = sys.argv[:] 50 | cmd = P.basename(args.pop(0)) 51 | 52 | if len(args) < 2: 53 | print("Usage: %s TIMESTAMP IDENTIFIER" % (cmd), file=sys.stderr) 54 | exit(1) 55 | 56 | timestamp = args.pop(0) 57 | identifiers = args 58 | 59 | try: 60 | timestamp = int(timestamp) 61 | except ValueError: 62 | timestamp, dot, frag = timestamp.partition('.') 63 | timestamp = (time.mktime( 64 | time.strptime(timestamp, "%Y-%m-%dT%H:%M:%S")) * 1000) 65 | if frag: 66 | assert len(frag) < 4 67 | timestamp += + int(frag) 68 | 69 | def faux_offset(s): 70 | return sum(ord(c) * ((i+1) ** 2) for i, c in enumerate(s)) 71 | 72 | for identifier in identifiers: 73 | offset = faux_offset(identifier) 74 | check = checksum(identifier) 75 | slug = librisencode(int(timestamp + offset), check) 76 | print("<{}> = <{}> # {}, {}".format(slug, identifier, offset, check)) 77 | -------------------------------------------------------------------------------- /lxltools/timeutil.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timezone 2 | 3 | 4 | def nowstamp(): 5 | return datetime.now().timestamp() 6 | 7 | 8 | def w3c_dtz_to_ms(ztime: str): 9 | assert ztime.endswith('Z') 10 | try: # fromisoformat is new in Python 3.7 11 | return int(datetime.fromisoformat(ztime.replace('Z', '+00:00')) 12 | .timestamp() * 1000) 13 | except AttributeError: # fallback can be removed when we rely on Py 3.7+ 14 | ztime = ztime[:-1] # drop 'Z' 15 | if '.' not in ztime: 16 | ztime += '.000' # add millisecs to comply with format 17 | ztime += '+0000' # strptime-compliant UTC timezone format 18 | return int(datetime.strptime(ztime, '%Y-%m-%dT%H:%M:%S.%f%z') 19 | .timestamp() * 1000) 20 | 21 | 22 | def to_w3c_dtz(ms: float): 23 | dt = datetime.fromtimestamp(ms / 1000, tz=timezone.utc) 24 | return dt.isoformat(timespec='milliseconds').replace('+00:00', 'Z') 25 | 26 | 27 | def to_http_date(s: float): 28 | return datetime.utcfromtimestamp(s).strftime('%a, %d %b %Y %H:%M:%S GMT') 29 | -------------------------------------------------------------------------------- /query.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals, print_function 2 | from rdflib import ConjunctiveGraph 3 | from rdflib.util import guess_format 4 | 5 | 6 | def run_query(source, query_file, context=None): 7 | graph = ConjunctiveGraph() 8 | fmt = ('json-ld' if source.endswith('.jsonld') else 9 | guess_format(source)) 10 | graph.parse(source, format=fmt, context=context) 11 | with open(query_file) as fp: 12 | query_text = fp.read().decode('utf-8') 13 | result = graph.query(query_text) 14 | for row in result: 15 | print('\t'.join(t.n3() if t else 'UNDEF' for t in row).encode('utf-8')) 16 | 17 | 18 | if __name__ == '__main__': 19 | from sys import argv 20 | args = argv[1:] 21 | source = args.pop(0) 22 | query_file = args.pop(0) 23 | context = 'build/vocab/context.jsonld' 24 | run_query(source, query_file, context) 25 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | setuptools==70.0.0 2 | rdflib==6.1.1 3 | pyRdfa3==3.5.3 4 | requests==2.32.3 5 | Markdown==3.7 6 | -------------------------------------------------------------------------------- /scripts/analyze-enums.py: -------------------------------------------------------------------------------- 1 | import re 2 | from collections import OrderedDict 3 | import json 4 | 5 | enums = json.load(open('source/enums.jsonld')) 6 | 7 | collection_sets = set() 8 | coll_values = {} 9 | 10 | for item in enums.get('@graph') or enums['enumDefs'].values(): 11 | colls = item.get('inCollection') 12 | if colls: 13 | for c in colls: 14 | coll_values.setdefault(c['@id'], []).append(item['@id']) 15 | collection_sets.add(tuple(sorted(c['@id'] for c in colls))) 16 | 17 | value_sets = {} 18 | 19 | for cset in collection_sets: 20 | for c in cset: 21 | name = c 22 | members = set(coll_values[c]) 23 | value_sets.setdefault(tuple(sorted(members)), set()).add(name) 24 | 25 | def common_key(terms): 26 | prev = set() 27 | keys = [] 28 | for term in terms: 29 | key = term.rsplit('/', 1)[-1] 30 | keys.append(key) 31 | parts = set(re.findall(r'[A-Z][^A-Z]*', term)) 32 | candidates = prev & parts if prev else parts 33 | if not candidates: 34 | candidates = parts | prev 35 | prev = candidates 36 | return "Or".join(keys) if len(candidates) > 1 else next(iter(candidates)) 37 | 38 | equivs = OrderedDict() 39 | for values, equiv_colls in value_sets.items(): 40 | if len(equiv_colls) > 1: 41 | equivs[common_key(equiv_colls)] =\ 42 | [{'values': values}] + \ 43 | list(equiv_colls) 44 | 45 | def dump(data): 46 | print(json.dumps(data, indent=2, sort_keys=True, separators=(',', ': '))) 47 | 48 | print "# Equivalent collections:" 49 | dump(equivs) 50 | 51 | print "# Map:" 52 | cmap = {} 53 | for key, paths in equivs.items(): 54 | cmap[key] = [path.rsplit('/', 1)[-1] for path in paths if isinstance(path, unicode)] 55 | dump(cmap) 56 | -------------------------------------------------------------------------------- /scripts/checkdisplay.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | project_dir = Path(__file__).parent.parent 5 | displaypath = project_dir / "source/vocab/display.jsonld" 6 | 7 | with displaypath.open() as f: 8 | display = json.load(f) 9 | 10 | for group in display["lensGroups"].values(): 11 | if g_id := group.get("@id"): 12 | print(g_id) 13 | for key, lens in group.get("lenses", {}).items(): 14 | cld = lens.get("classLensDomain") 15 | if key != cld: 16 | if cld is None: 17 | print(f" {key} - Missing classLensDomain") 18 | else: 19 | print(f" {key} - Different classLensDomain: {cld}") 20 | print() 21 | -------------------------------------------------------------------------------- /scripts/construct.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | 4 | from rdflib import ConjunctiveGraph 5 | from rdflib.plugin import Parser, register 6 | 7 | register("text/html", Parser, "pyRdfa.rdflibparsers", "RDFaParser") 8 | 9 | queryfile, *infiles = sys.argv[1:] 10 | 11 | cg = ConjunctiveGraph() 12 | for infile in infiles: 13 | cg.parse(infile) 14 | 15 | with open(queryfile) as f: 16 | res = cg.query(f.read()) 17 | 18 | res.graph.namespace_manager = cg.namespace_manager 19 | res.serialize(sys.stdout.buffer, format='turtle') 20 | -------------------------------------------------------------------------------- /scripts/create_i18n_datasets.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import json 3 | import re 4 | import sys 5 | 6 | ID = "@id" 7 | TYPE = "@type" 8 | VALUE = "@value" 9 | GRAPH = "@graph" 10 | 11 | FORM_RE = re.compile(r'(\w+)-(\w+)-t-(\w+)(?:-(?:(\w+)-)?([xm]0-.+))?') 12 | COMBINATIONS = [ 13 | ('el', 'Grek', 'x0-btj'), 14 | ('grc', 'Grek', 'x0-skr-1980'), 15 | ('mn', 'Cyrl', 'x0-lessing'), 16 | 17 | ('be', 'Cyrl', 'm0-iso-1968'), 18 | ('bg', 'Cyrl', 'm0-iso-1968'), 19 | ('mk', 'Cyrl', 'm0-iso-1968'), 20 | ('ru', 'Cyrl', 'm0-iso-1968'), 21 | ('sr', 'Cyrl', 'm0-iso-1968'), 22 | ('uk', 'Cyrl', 'm0-iso-1968'), 23 | 24 | ('be', 'Cyrl', 'm0-iso-1995'), 25 | ('bg', 'Cyrl', 'm0-iso-1995'), 26 | ('kk', 'Cyrl', 'm0-iso-1995'), 27 | ('mk', 'Cyrl', 'm0-iso-1995'), 28 | ('ru', 'Cyrl', 'm0-iso-1995'), 29 | ('sr', 'Cyrl', 'm0-iso-1995'), 30 | ('uk', 'Cyrl', 'm0-iso-1995'), 31 | 32 | ('am', 'Ethi', 'm0-alaloc'), 33 | ('ar', 'Arab', 'm0-alaloc'), 34 | ('as', 'Beng', 'm0-alaloc'), 35 | ('az', 'Arab', 'm0-alaloc'), 36 | ('az', 'Cyrl', 'm0-alaloc'), 37 | ('bo', 'Tibt', 'm0-alaloc'), 38 | ('ban', 'Bali', 'm0-alaloc'), 39 | ('btk', 'Batk', 'm0-alaloc'), 40 | ('chr', 'Cher', 'm0-alaloc'), 41 | ('chu', 'Cyrs', 'm0-alaloc'), 42 | ('cop', 'Copt', 'm0-alaloc'), 43 | ('dv', 'Thaa', 'm0-alaloc'), 44 | ('fa', 'Arab', 'm0-alaloc'), 45 | ('gu', 'Gujr', 'm0-alaloc'), 46 | ('he', 'Hebr', 'm0-alaloc'), 47 | ('hi', 'Deva', 'm0-alaloc'), 48 | ('hy', 'Armn', 'm0-alaloc'), 49 | ('iu', 'Cans', 'm0-alaloc'), 50 | ('jrb', 'Hebr', 'm0-alaloc'), 51 | ('jv', 'Java', 'm0-alaloc'), 52 | ('kir', 'Cyrl', 'm0-alaloc'), 53 | ('km', 'Khmr', 'm0-alaloc'), 54 | ('kn', 'Knda', 'm0-alaloc'), 55 | ('ko', 'Hang', 'm0-alaloc'), 56 | ('ks', 'Arab', 'm0-alaloc'), 57 | ('ku', 'Cyrl', 'm0-alaloc'), 58 | ('ku', 'Arab', 'm0-alaloc'), 59 | ('lad', 'Hebr', 'm0-alaloc'), 60 | ('lo', 'Laoo', 'm0-alaloc'), 61 | ('mad', 'Java', 'm0-alaloc'), 62 | ('ml', 'Mlym', 'm0-alaloc'), 63 | ('mn', 'Mong', 'm0-alaloc'), 64 | ('mnc', 'Mong', 'm0-alaloc'), 65 | ('mr', 'Deva', 'm0-alaloc'), 66 | ('my', 'Mymr', 'm0-alaloc'), 67 | ('or', 'Orya', 'm0-alaloc'), 68 | ('pa', 'Guru', 'm0-alaloc'), 69 | ('pi', 'Beng', 'm0-alaloc'), 70 | ('pi', 'Mymr', 'm0-alaloc'), 71 | ('pi', 'Deva', 'm0-alaloc'), 72 | ('pi', 'Sinh', 'm0-alaloc'), 73 | ('pi', 'Thai', 'm0-alaloc'), 74 | ('ps', 'Arab', 'm0-alaloc'), 75 | ('sat', 'Olck', 'm0-alaloc'), 76 | ('sd', 'Arab', 'm0-alaloc'), 77 | ('sd', 'Deva', 'm0-alaloc'), 78 | ('shn', 'Mymr', 'm0-alaloc'), 79 | ('si', 'Sinh', 'm0-alaloc'), 80 | ('su', 'Java', 'm0-alaloc'), 81 | ('syc', 'Syrc', 'm0-alaloc'), 82 | ('syr', 'Syrc', 'm0-alaloc'), 83 | ('ta', 'Taml', 'm0-alaloc'), 84 | ('te', 'Telu', 'm0-alaloc'), 85 | ('tg', 'Cyrl', 'm0-alaloc'), 86 | ('th', 'Thai', 'm0-alaloc'), 87 | ('ti', 'Ethi', 'm0-alaloc'), 88 | ('tk', 'Cyrl', 'm0-alaloc'), 89 | ('tt', 'Cyrl', 'm0-alaloc'), 90 | ('ug', 'Arab', 'm0-alaloc'), 91 | ('ug', 'Cyrl', 'm0-alaloc'), 92 | ('ur', 'Arab', 'm0-alaloc'), 93 | ('uz', 'Arab', 'm0-alaloc'), 94 | ('uz', 'Cyrl', 'm0-alaloc'), 95 | ('vai', 'Vaii', 'm0-alaloc'), 96 | ('yi', 'Hebr', 'm0-alaloc'), 97 | ('zh', 'Hani', 'm0-alaloc'), 98 | ] 99 | 100 | LANG_FORM = "TransformedLanguageForm" 101 | 102 | ALA_LOC_NO_SCRIPT = ['tmh', 'ka'] 103 | 104 | ALA_LOC_NON_SLAVIC_CYRILLIC = [ 105 | 'abk', 'ady', 'alt', 'ava', 'bak', 'bua', 'che', 'chm', 'chv', 'dar', 106 | 'inh', 'kaa', 'kbd', 'kom', 'krc', 'krl', 'kum', 'lez', 'lit', 'nog', 107 | 'oss', 'rom', 'rum', 'rum', 'sah', 'sel', 'tut', 'udm', 'xal', 108 | ] 109 | 110 | forms = [ 111 | f"{tag}-Latn-t-{tag}-{script}-{rules}" 112 | for tag, script, rules in COMBINATIONS 113 | ] + [ 114 | f"{tag}-Latn-t-{tag}-m0-alaloc" 115 | for tag in ALA_LOC_NO_SCRIPT 116 | ] + [ 117 | f"{tag}-Latn-t-{tag}-Cyrl-m0-alaloc" 118 | for tag in ALA_LOC_NON_SLAVIC_CYRILLIC 119 | ] + [ 120 | f"und-Latn-t-und", 121 | ] 122 | 123 | 124 | LANG_CODE_MAP: dict[str, str] = {} 125 | with open('build/languages.json.lines') as f: 126 | for l in f: 127 | thing = json.loads(l) 128 | if GRAPH in thing: 129 | thing = thing[GRAPH][1] 130 | if thing[TYPE] == 'Language': 131 | tag = thing.get('langTag') 132 | if tag: 133 | LANG_CODE_MAP[tag] = thing['code'] 134 | 135 | 136 | def get_langlink(tag): 137 | # TODO: Decide to EITHER lookup id: 138 | code = LANG_CODE_MAP.get(tag, tag) 139 | return {ID: f"/language/{code}"} 140 | # OR use tag-based sameAs alias to all languages (not done yet): 141 | #return {ID: f"/i18n/lang/{tag}"} 142 | # OR rely on LanguageLinker: 143 | #return {TYPE: "Language", "code": tag} 144 | 145 | 146 | tags: set[str] = set() 147 | scripts = set() 148 | rules = set() 149 | 150 | items = [] 151 | 152 | 153 | def describe(code, cat, rtype): 154 | desc = {ID: f"/i18n/{cat}/{code}", TYPE: rtype, "code": code} 155 | items.append(desc) 156 | return desc 157 | 158 | 159 | for form in forms: 160 | desc = describe(form, 'lang', LANG_FORM) 161 | for tag, script, otag, oscript, rule in FORM_RE.findall(form): 162 | code = desc.pop("code") 163 | desc["code"] = [{VALUE: code, TYPE: 'BCP47'}] 164 | tags.add(tag) 165 | tags.add(otag) 166 | langref = get_langlink(tag) 167 | if tag != otag: 168 | desc["inLanguage"] = langref 169 | desc["fromLanguage"] = get_langlink(otag) 170 | else: 171 | # TODO: which is better/worse for applications? 172 | # (Note 1: We do use broader for specialized languages). 173 | # (Note 2: We've defined inLanguage as a subPropertyOf broader). 174 | #desc["broader"] = langref 175 | desc["inLanguage"] = langref 176 | 177 | desc["inLangScript"] = {ID: f"/i18n/script/{script}"} 178 | scripts.add(script) 179 | 180 | if oscript: 181 | desc["fromLangScript"] = {ID: f"/i18n/script/{oscript}"} 182 | scripts.add(oscript) 183 | 184 | if rule: 185 | rule = rule.replace('0-', '0/') # TODO: is this what we want? 186 | desc["langTransformAccordingTo"] = {ID: f"/i18n/rule/{rule}"} 187 | rules.add(rule) 188 | 189 | for script in scripts: 190 | describe(script, 'script', 'LanguageScript') 191 | 192 | for rule in rules: 193 | base, code = rule.split('/') 194 | desc = describe(code, f'rule/{base}', 'LanguageTransformRules') 195 | desc['inCollection'] = {ID: f'/i18n/rule/{base}'} 196 | 197 | json.dump({"@graph": items}, sys.stdout, indent=2, ensure_ascii=True) 198 | -------------------------------------------------------------------------------- /scripts/create_sab_skos_data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | from __future__ import unicode_literals 3 | import csv 4 | import json 5 | import re 6 | import sys 7 | import urllib 8 | 9 | 10 | SKOS = "http://www.w3.org/2004/02/skos/core#" 11 | SAB_BASE = "https://id.kb.se/def/scheme/sab/{0}" 12 | DDC_BASE = "http://dewey.info/class/{0}/" 13 | LANG = 'sv' 14 | 15 | hint_link_map = { 16 | #'#H': SKOS+'closeMatch', 17 | #'#G': SKOS+'closeMatch', 18 | #'#M': inverseOf SKOS+'closeMatch', 19 | '#1': SKOS+'exactMatch', 20 | '#2': SKOS+'broadMatch', 21 | '#3': SKOS+'narrowMatch', 22 | '#4': SKOS+'closeMatch', 23 | 'DDK22': SKOS+'relatedMatch', # TODO 24 | } 25 | 26 | def create_data(sab_codes_fpath, ddc_map_fpath, limit): 27 | rmap = {} 28 | create_sab_skos_data(rmap, sab_codes_fpath, limit=limit) 29 | create_sab_ddc_data(rmap, ddc_map_fpath) 30 | return { 31 | "@context": { 32 | "@vocab": SKOS, 33 | "prefLabel": {"@language": LANG} 34 | }, 35 | "@graph": rmap.values() 36 | } 37 | 38 | def to_uri(base, code): 39 | slug = urllib.quote(code.encode('utf-8'), safe=b':(),') 40 | return base.format(slug) 41 | 42 | def create_sab_skos_data(rmap, fpath, limit=0): 43 | label_map = {} 44 | pending_broader = [] 45 | 46 | for i, (code, label) in enumerate(read_csv_items(fpath)): 47 | uri = to_uri(SAB_BASE, code) 48 | r = rmap[uri] = { 49 | "@id": uri, 50 | "@type": "Concept", 51 | "notation": code, 52 | "prefLabel": label 53 | } 54 | label_map[label] = uri 55 | if ': ' in label: 56 | pending_broader.append((r, label.rsplit(': ', 1)[0])) 57 | if limit and i > limit: 58 | break 59 | 60 | for r, broader_label in pending_broader: 61 | broader_uri = label_map.get(broader_label) 62 | if broader_uri: 63 | r.setdefault("broader", []).append({"@id": broader_uri}) 64 | 65 | def create_sab_ddc_data(rmap, fpath): 66 | for number, sab_code, ddc_code, hint in read_csv_items( 67 | fpath, skip_comment=True, coding='utf-8', size=4): 68 | hint = re.split(r'\s+|\w(?=#)', hint)[-1].strip() 69 | link = hint_link_map.get(hint) 70 | if not link: 71 | print >> sys.stderr, "No link map for", hint.encode('utf-8') 72 | continue 73 | uri = to_uri(SAB_BASE, sab_code) 74 | rmap.setdefault(uri, {"@id": uri}).setdefault(link, []).append( 75 | {"@id": to_uri(DDC_BASE, ddc_code)}) 76 | 77 | def read_csv_items(fpath, skip_first=True, skip_comment=False, 78 | csv_dialect='excel-tab', coding='latin-1', size=0): 79 | with open(fpath, 'rb') as fp: 80 | reader = csv.reader(fp, csv_dialect) 81 | if skip_first is True: 82 | reader.next() 83 | for row in reader: 84 | if not row or skip_comment and row[0].startswith(b'#'): 85 | continue 86 | cols = [col.strip().decode(coding) for col in row] 87 | if size and len(cols) > size: 88 | cols = cols[0:size] 89 | yield cols 90 | 91 | 92 | if __name__ == '__main__': 93 | args = sys.argv[1:] 94 | sab_codes_fpath = args.pop(0) 95 | ddc_map_fpath = args.pop(0) 96 | limit = int(args.pop(0)) if args else 0 97 | 98 | data = create_data(sab_codes_fpath, ddc_map_fpath, limit) 99 | print json.dumps(data, 100 | indent=2, separators=(',', ': '), sort_keys=True, ensure_ascii=False 101 | ).encode('utf-8') 102 | -------------------------------------------------------------------------------- /scripts/digest-marcframe.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals, print_function 2 | try: str, bytes = unicode, str 3 | except: pass 4 | import json 5 | from itertools import chain 6 | 7 | 8 | def digest_marcframe(marcframe): 9 | print(' VALUES (?property ?class ?enumclass) {') 10 | digest_fixedfields(marcframe) 11 | print(' }') 12 | print() 13 | 14 | print(' VALUES (?property ?class ?enumclass) {') 15 | for part in ['patterns', 'bib', 'auth', 'hold']: 16 | digest_fields(marcframe, part) 17 | print(' }') 18 | 19 | 20 | def digest_fixedfields(marcframe): 21 | 22 | def parse_fixed_by_section(part, tag): 23 | for basename, coldefs in marcframe[part][tag].items(): 24 | if basename.startswith('TODO') or not basename[0].isupper(): 25 | continue 26 | yield from _parse_coldefs(part, tag, coldefs, basename) 27 | 28 | def parse_fixed(part, tag): 29 | yield from _parse_coldefs(part, tag, marcframe[part][tag]) 30 | 31 | def _parse_coldefs(part, tag, coldefs, basename=None): 32 | if isinstance(coldefs, str): 33 | return 34 | for col_key, col_dfn in coldefs.items(): 35 | if not isinstance(col_dfn, dict): 36 | continue 37 | link = col_dfn.get('link') or col_dfn.get('addLink') 38 | tokenmap = col_dfn.get('tokenMap') 39 | if not link or not tokenmap: 40 | continue 41 | if col_key.startswith('['): 42 | bot, ceil = map(int, col_key[1:-1].split(':')) 43 | col_key = str(bot) if ceil == bot + 1 else '{}_{}'.format(bot, ceil - 1) 44 | 45 | marcsource = 'marc:{part}/{tag}/{col_key}'.format(**vars()) 46 | yield (link, basename, tokenmap, marcsource) 47 | 48 | colprops = {} 49 | for link, basename, tokenmap, marcsource in chain( 50 | parse_fixed('bib', '000'), 51 | parse_fixed_by_section('bib', '006'), 52 | parse_fixed_by_section('bib', '007'), 53 | parse_fixed_by_section('bib', '008'), 54 | parse_fixed('auth', '000'), 55 | parse_fixed('hold', '000'), 56 | parse_fixed('hold', '008'), 57 | ): 58 | colprops.setdefault((link, basename, tokenmap), []).append(marcsource) 59 | 60 | prev = None 61 | for colprop in sorted(colprops): 62 | link, basename, tokenmap = colprop 63 | if prev and link != prev and not (':' in prev and ':' in link): 64 | print() 65 | prev = link 66 | if ':' not in link: 67 | link = ':' + link 68 | basename = ':' + basename if basename else 'UNDEF' 69 | marcsources = ', '.join(colprops[colprop]) 70 | print(" ({link}\t{basename}\tmarc:{tokenmap}) # {marcsources}".format(**vars())) 71 | 72 | 73 | def digest_fields(marcframe, part): 74 | defs = marcframe[part] 75 | for key, dfn in defs.items(): 76 | if not key.isdigit(): 77 | continue # TODO: parse patterns! 78 | #dfn.get('match') 79 | link = dfn.get('link') or dfn.get('addLink') 80 | prop = dfn.get('property') or dfn.get('addProperty') 81 | rtype = dfn.get('resourceType') 82 | domain = 'UNDEF' 83 | marcsources = ', '.join([]) # TODO 84 | print(" ({domain}\t{link}\t{rtype}) # {marcsources}".format(**vars())) 85 | 86 | 87 | if __name__ == '__main__': 88 | import sys, os 89 | args = sys.argv[1:] 90 | source = args.pop(0) if args else '../whelk-core/src/main/resources/ext/marcframe.json' 91 | 92 | with open(source) as fp: 93 | marcframe = json.load(fp) 94 | 95 | digest_marcframe(marcframe) 96 | -------------------------------------------------------------------------------- /scripts/extract-marcframe-terms.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals, print_function, division 2 | from collections import Counter 3 | import json 4 | 5 | 6 | def get_keywords(marcframe, ignore_speced=False): 7 | keywords = Counter() 8 | speced = set() 9 | for key, part in marcframe.items(): 10 | if not isinstance(part, dict): 11 | continue 12 | for field in part.values(): 13 | if not field: 14 | continue 15 | is_speced = any(kw in field for kw in ['_specSource', '_spec']) 16 | if not isinstance(field, dict): 17 | #print "Skipping:", field 18 | continue 19 | for kw, obj in field.items(): 20 | if '$' in kw or kw in {'i1', 'i2'} or kw[0].isupper(): 21 | if obj: 22 | if not isinstance(obj, dict): 23 | continue 24 | for subkw, subobj in obj.items(): 25 | if subkw.startswith('['): 26 | keywords.update(subobj.keys()) 27 | if is_speced: 28 | speced |= set(subobj) 29 | else: 30 | keywords[subkw] += 1 31 | if is_speced: 32 | speced.add(kw) 33 | elif not kw.startswith('['): 34 | keywords[kw] += 1 35 | if is_speced: 36 | speced.add(kw) 37 | if ignore_speced: 38 | for k in speced: 39 | del keywords[k] 40 | return keywords 41 | 42 | 43 | if __name__ == '__main__': 44 | import sys 45 | args = sys.argv[1:] 46 | source = args.pop(0) 47 | 48 | with open(source) as fp: 49 | marcframe = json.load(fp) 50 | 51 | keywords = get_keywords(marcframe) 52 | for kw, i in keywords.most_common(): 53 | if 'NOTE:' in kw.upper() or 'TODO' in kw.upper(): 54 | continue 55 | print(kw, i) 56 | -------------------------------------------------------------------------------- /scripts/get-model-terms-from-example-usage.py: -------------------------------------------------------------------------------- 1 | """ 2 | Just a throw-away script for making a reduced example vocabulary from 3 | the core vocabulary and some supplied example usage data. 4 | """ 5 | 6 | from __future__ import unicode_literals, print_function 7 | import json 8 | from rdflib import Graph, BNode 9 | from rdflib.namespace import OWL, RDF, RDFS, SKOS 10 | from os import path as P 11 | import sys 12 | 13 | 14 | args = sys.argv[1:] 15 | if len(args) < 2: 16 | print("Usage: SCRIPT DATA_FILE CONTEXT_FILE", file=sys.stderr) 17 | exit(1) 18 | fpath = args.pop(0) 19 | ctx = args.pop(0) 20 | 21 | 22 | # Load the core vocabulary located nearby... 23 | vocpath = P.join(P.dirname(__file__), '../def/terms.ttl') 24 | vocab = Graph().parse(vocpath, format='turtle') 25 | 26 | 27 | # Get "real" vocab uri and use instead of example vocab 28 | for s in vocab.subjects(RDF.type, OWL.Ontology): 29 | vocab_uri = unicode(s) 30 | break 31 | 32 | with open(ctx) as fp: 33 | ctx = json.load(fp)['@context'] 34 | ctx['@vocab'] = vocab_uri 35 | 36 | 37 | # Collect used predicates and types 38 | terms = set() 39 | examples = Graph().parse(fpath, format="json-ld", context=ctx) 40 | for p, o in examples.predicate_objects(): 41 | if p == RDF.type: 42 | terms.add(o) 43 | terms |= set(o for o in vocab.objects(o, (OWL.equivalentClass | RDFS.subClassOf)*'+') 44 | if not isinstance(o, BNode)) 45 | else: 46 | terms.add(p) 47 | terms |= set(o for o in vocab.objects(p, (OWL.equivalentProperty | RDFS.subPropertyOf)*'+') 48 | if not isinstance(o, BNode)) 49 | 50 | 51 | # Gather used parts described with a limited selection of classes and predicates 52 | parts = Graph() 53 | parts.namespace_manager = vocab.namespace_manager 54 | 55 | textprops = {RDFS.label, RDFS.comment, 56 | SKOS.prefLabel, SKOS.altLabel, SKOS.definition, SKOS.note} 57 | props = {RDFS.subPropertyOf, RDFS.subClassOf, 58 | OWL.equivalentProperty, OWL.equivalentClass} | { 59 | RDFS.domain, RDFS.range 60 | } | textprops 61 | types = {OWL.Class, OWL.DatatypeProperty, OWL.ObjectProperty, OWL.Restriction, RDFS.Datatype} 62 | for t in terms: 63 | for p, o in vocab.predicate_objects(t): 64 | if p in props or p == RDF.type and o in types: 65 | if isinstance(o, BNode): 66 | continue 67 | parts.add((t,p, o)) 68 | 69 | missing = sorted(parts.qname(t) for t in (terms - set(parts.subjects()))) 70 | if missing: 71 | print("# Missing:", ", ".join(missing), file=sys.stderr) 72 | 73 | 74 | # Make a nice compact context for the output model 75 | ctx = {} 76 | for t in props | types: 77 | key = vocab.qname(t).split(':')[-1] 78 | if t in textprops: 79 | ctx[key + 'ByLang'] = {"@id": unicode(t), "@container": "@language"} 80 | ctx[key] = unicode(t) 81 | for pfx, ns in vocab.namespaces(): 82 | ctx[pfx if pfx else '@vocab'] = ns 83 | 84 | jstr = parts.serialize(format='json-ld', context=ctx, sort_keys=True) 85 | 86 | # Then clean it up further... 87 | model = json.loads(jstr) 88 | model['@graph'].sort(key=lambda it: it['@id']) 89 | model['@context'] = "/sys/context.jsonld" 90 | s = json.dumps(model, indent=2, separators=(',', ': '), sort_keys=True, ensure_ascii=False) 91 | 92 | for pfx, ns in vocab.namespaces(): 93 | s = s.replace(ns, pfx + ':' if pfx else '') 94 | 95 | import re 96 | s = re.sub(r'{\s+(\S+: "[^"]*")\s+}', r'{\1}', s) 97 | s = re.sub(r'\[\s+([^,]+?),\s+([^,]+?)\s+\]', r'[\1, \2]', s) 98 | s = re.sub(r'{\s+([^,]+?),\s+([^,]+?)\s+}', r'{\1, \2}', s) 99 | 100 | print(s.encode('utf-8')) 101 | -------------------------------------------------------------------------------- /scripts/get-proxy-domains-ranges.rq: -------------------------------------------------------------------------------- 1 | prefix rdf: 2 | prefix rdfs: 3 | prefix owl: 4 | prefix : 5 | 6 | construct { 7 | 8 | ?property rdfs:domain ?domain . 9 | ?property rdfs:range ?range . 10 | 11 | } where { 12 | 13 | bind(str(:) as ?strvocab) 14 | 15 | ?property 16 | rdfs:subPropertyOf|owl:equivalentProperty ?baseproperty . 17 | { 18 | ?baseproperty rdfs:domain ?basedomain . 19 | ?domain (rdfs:subClassOf|owl:equivalentClass)* ?basedomain . 20 | } union { 21 | ?baseproperty rdfs:range ?baserange . 22 | ?range (rdfs:subClassOf|owl:equivalentClass)* ?baserange . 23 | } 24 | 25 | # ... rdfs:isDefinedBy ?vocab . 26 | filter(strstarts(str(?property), ?strvocab)) 27 | 28 | filter(!bound(?domain) || 29 | strstarts(str(?domain), ?strvocab)) 30 | 31 | filter(!bound(?range) || 32 | strstarts(str(?range), ?strvocab)) 33 | 34 | } 35 | -------------------------------------------------------------------------------- /scripts/load-defs-whelk.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | WHELK=$1 4 | FILTER=$2 5 | DOBUILD=$3 6 | BUILDBASE="build" 7 | 8 | put() { 9 | if [[ $3 == *"$FILTER"* ]]; then 10 | echo "PUT '$1' to <$3> as $2" 11 | curl -XPUT -H "Content-Type:$2" --data-binary @$1 $3?data 12 | fi 13 | } 14 | 15 | # Generate datasets 16 | if [[ $DOBUILD != "--nobuild" && $FILTER == "" ]]; then 17 | echo "Compile definitions..." 18 | python datasets.py -c cache/ -o $BUILDBASE/ 19 | echo "Done." 20 | fi 21 | 22 | # Load JSON-LD contexts 23 | libcontext=$BUILDBASE/lib-context.jsonld 24 | put $libcontext application/ld+json ${WHELK}/sys/context/lib.jsonld 25 | for ctx in owl skos; do 26 | put sys/context/${ctx}.jsonld application/ld+json ${WHELK}/sys/context/${ctx}.jsonld 27 | done 28 | 29 | # Load standalone documents 30 | for doc in terms schemes; do 31 | put $BUILDBASE/${doc}.jsonld application/ld+json ${WHELK}/def/$doc 32 | done 33 | 34 | # Load datasets 35 | for dataset in languages countries nationalities relators enum/{content,carrier,record}; do 36 | for file in $BUILDBASE/$dataset/*.jsonld; do 37 | slug=$(basename -s .jsonld $file) 38 | put $file application/ld+json ${WHELK}/def/${dataset}/$slug 39 | done 40 | done 41 | -------------------------------------------------------------------------------- /scripts/make-lexical-rda.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from rdflib import Graph 3 | from os import path as P 4 | import sys 5 | 6 | RDA_SOURCE_NAMES = ['a', 'c', 'w', 'e', 'm', 'i', 'z'] 7 | 8 | g = Graph() 9 | 10 | for name in RDA_SOURCE_NAMES: 11 | g.parse("http://rdaregistry.info/Elements/{}.ttl".format(name), format='turtle') 12 | 13 | with open(P.join(P.dirname(__file__), 'make-lexical-rda.rq')) as fp: 14 | res = g.query(fp) 15 | 16 | for name in RDA_SOURCE_NAMES: 17 | res.graph.namespace_manager.bind(name, 18 | "http://rdaregistry.info/Elements/{}/".format(name)) 19 | 20 | res.graph.serialize(sys.stdout, format='turtle') 21 | -------------------------------------------------------------------------------- /scripts/make-lexical-rda.rq: -------------------------------------------------------------------------------- 1 | prefix owl: 2 | prefix reg: 3 | 4 | construct { 5 | ?alias owl:sameAs ?s ; ?p ?bound_o . 6 | } where { 7 | ?s ?p ?o . 8 | FILTER(?p != reg:lexicalAlias) 9 | ?s reg:lexicalAlias ?alias . 10 | OPTIONAL { ?o reg:lexicalAlias ?alias_o . } 11 | BIND(COALESCE(?alias_o, ?o) as ?bound_o) 12 | } 13 | -------------------------------------------------------------------------------- /scripts/make_rda_mappings.py: -------------------------------------------------------------------------------- 1 | from rdflib import * 2 | from rdflib.namespace import * 3 | 4 | # Monkey-patching RDFLib 5 | # Borks on XML Literals where a declares xmlns is *also* declared as default xmlns. 6 | from rdflib.plugins.parsers.rdfxml import RDFXMLHandler 7 | RDFXMLHandler_startPrefixMapping = RDFXMLHandler.startPrefixMapping 8 | def patched_startPrefixMapping(self, prefix, namespace): 9 | current_prefix = self._current_context.get(namespace) 10 | if prefix is None and current_prefix: 11 | return 12 | return RDFXMLHandler_startPrefixMapping(self, prefix, namespace) 13 | RDFXMLHandler.startPrefixMapping = patched_startPrefixMapping 14 | 15 | 16 | BF2 = Namespace('http://id.loc.gov/ontologies/bibframe/') 17 | MADSRDF = Namespace('http://www.loc.gov/mads/rdf/v1#') 18 | 19 | KBRDA = Namespace('https://id.kb.se/rda/') 20 | KBV = Namespace('https://id.kb.se/vocab/') 21 | 22 | RDAContentType = URIRef('http://rdaregistry.info/termList/RDAContentType') 23 | LCContentType = URIRef('http://id.loc.gov/vocabulary/contentTypes') 24 | 25 | RDAMediaType = URIRef('http://rdaregistry.info/termList/RDAMediaType') 26 | LCMediaType = URIRef('http://id.loc.gov/vocabulary/mediaTypes') 27 | 28 | RDACarrierType = URIRef('http://rdaregistry.info/termList/RDACarrierType') 29 | LCCarrierType = URIRef('http://id.loc.gov/vocabulary/carriers') 30 | 31 | RDAIssuanceType = URIRef('http://rdaregistry.info/termList/ModeIssue') 32 | LCIssuanceType = URIRef('http://id.loc.gov/vocabulary/issuance') 33 | 34 | LANGS = ('en', 'sv') 35 | 36 | PROP_MAP = { 37 | SKOS.definition: KBV.definition, 38 | SKOS.prefLabel: KBV.prefLabel, 39 | SKOS.scopeNote: KBV.scopeNote 40 | } 41 | 42 | 43 | def make_rda_mappings(): 44 | g2 = Graph() 45 | g2.namespace_manager.bind('owl', OWL) 46 | g2.namespace_manager.bind('bf2', BF2) 47 | g2.namespace_manager.bind('rdacontent', RDAContentType) 48 | g2.namespace_manager.bind('rdamedia', RDAMediaType) 49 | g2.namespace_manager.bind('rdacarrier', RDACarrierType) 50 | g2.namespace_manager.bind('rdaissuance', RDAIssuanceType) 51 | g2.namespace_manager.bind('kbrda', KBRDA) 52 | g2.namespace_manager.bind('kbv', KBV) 53 | 54 | _make_mappings(g2, RDAContentType, LCContentType, KBV.ContentType) 55 | 56 | _make_mappings(g2, RDAMediaType, LCMediaType, KBV.MediaType) 57 | 58 | carrier_g = _make_mappings(g2, RDACarrierType, LCCarrierType, KBV.CarrierType) 59 | 60 | hack_media_id_by_chopped_top_carrier = {} 61 | 62 | for top in carrier_g.objects(RDACarrierType, SKOS.hasTopConcept): 63 | for top_id in g2.subjects(OWL.sameAs, top): 64 | break 65 | assert unicode(top_id).endswith('Carriers(Deprecated)'), top_id 66 | g2.remove((top_id, None, None)) 67 | # Redundant now; see comment just below. 68 | media_id = URIRef(unicode(top_id).replace('Carriers(Deprecated)', '')) 69 | if unicode(media_id).endswith('ProjectedImage') and (media_id, None, None) not in g2: 70 | media_id = URIRef(unicode(media_id).replace('ProjectedImage', 'Projected')) 71 | #g2.add((top_id, OWL.sameAs, media_id)) 72 | # NOTE: Before deprecation in RDA, it was possible to follow broader links 73 | # from specific carriers to base carrier terms which were equatable to one 74 | # of the media terms. These broader links are now missing. The loop below 75 | # thus doesn't execute anymore (no broader link is present in the RDA 76 | # source data.) 77 | # (e.g. kbrda:ComputerChipCartridge rdfs:subClassOf kbrda:Computer .) 78 | if (media_id, None, None) in g2: 79 | for narrower in carrier_g.subjects(SKOS.broader, top): 80 | for specific_carrier in g2.subjects(OWL.sameAs, narrower): 81 | g2.add((specific_carrier, RDFS.subClassOf, media_id)) 82 | # HACK instead: 83 | hack_media_id_by_chopped_top_carrier[unicode(top)[:-1]] = media_id 84 | 85 | # HACK to get the carrier media subclass relations... 86 | # 1. Match toplevel(Deprecated) and find carrier URIs starting with their iri[:-1] 87 | for carrier in g2.subjects(RDF.type, KBV.CarrierType): 88 | if carrier == KBRDA.MicroscopeSlide: 89 | continue 90 | for sameas in g2.objects(carrier, OWL.sameAs): 91 | media_id = hack_media_id_by_chopped_top_carrier.get(unicode(sameas[:-1])) 92 | if media_id: 93 | g2.add((carrier, RDFS.subClassOf, media_id)) 94 | break 95 | # 2. Complement by a crude string matching 96 | carriers = set(g2.subjects(RDF.type, KBV.CarrierType)) 97 | medias = set(unicode(media) for media in g2.subjects(RDF.type, KBV.MediaType)) 98 | for media in medias: 99 | for carrier in carriers: 100 | if unicode(carrier).startswith(media): 101 | g2.add((carrier, RDFS.subClassOf, URIRef(media))) 102 | # 3. Add remaining manually 103 | g2.add((KBRDA.MicroscopeSlide, RDFS.subClassOf, KBRDA.Microscopic)) 104 | g2.add((KBRDA.FilmRoll, RDFS.subClassOf, KBRDA.Projected)) 105 | 106 | _make_mappings(g2, RDAIssuanceType, LCIssuanceType, KBV.IssuanceType) 107 | 108 | return g2 109 | 110 | 111 | def _make_mappings(g2, rda_type_scheme, lc_type_scheme, rtype): 112 | data_url = str(rda_type_scheme) + '.ttl' 113 | g = Graph().parse(data_url, format='turtle') 114 | g.parse(lc_type_scheme) 115 | for s in g.subjects(SKOS.inScheme, rda_type_scheme): 116 | for p, preflabel_en in g.preferredLabel(s, 'en'): 117 | symbol = preflabel_en.title().replace(' ', '').replace('-', '') 118 | 119 | uri = KBRDA[symbol] 120 | g2.add((uri, RDF.type, OWL.Class)) 121 | g2.add((uri, RDF.type, rtype)) 122 | 123 | # TODO: either type or subClassOf... 124 | # g2.add((uri, RDFS.subClassOf, rtype)) 125 | 126 | g2.add((uri, OWL.sameAs, s)) 127 | for p2 in (SKOS.definition, SKOS.prefLabel, SKOS.scopeNote): 128 | for l in g.objects(s, p2): 129 | if l.language in LANGS: 130 | g2.add((uri, PROP_MAP[p2], l)) 131 | 132 | for lc_def in g.subjects(SKOS.prefLabel, Literal(unicode(preflabel_en))): 133 | if (lc_def, RDF.type, MADSRDF.Authority) in g: 134 | lc_code = unicode(lc_def).rsplit('/', 1)[-1] 135 | g2.add((uri, KBV.code, Literal(lc_code))) 136 | g2.add((uri, OWL.sameAs, lc_def)) 137 | 138 | return g 139 | 140 | 141 | if __name__ == '__main__': 142 | import sys 143 | 144 | g = make_rda_mappings() 145 | g.serialize(sys.stdout, format='turtle') 146 | -------------------------------------------------------------------------------- /scripts/map-mads-from-skos.rq: -------------------------------------------------------------------------------- 1 | prefix rdfs: 2 | prefix owl: 3 | prefix skos: 4 | prefix madsrdf: 5 | 6 | construct { 7 | 8 | ?term owl:equivalentProperty ?madsterm . 9 | 10 | } where { 11 | 12 | ?term owl:equivalentProperty ?skosterm . 13 | 14 | FILTER(!strstarts(str(?term), str(skos:))) 15 | FILTER(!strstarts(str(?term), str(madsrdf:))) 16 | 17 | ?madsterm rdfs:subPropertyOf ?skosterm . 18 | FILTER(!strstarts(str(?madsterm), str(skos:)) 19 | && strstarts(str(?skosterm), str(skos:))) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /scripts/marc/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Generate the marcmap ## 3 | 4 | (Ask a co-worker for the config file sources.) 5 | 6 | 1. Get Swedish legacy config files and put them in a dir: 7 | 8 | $ CONFIG_SV=... 9 | 10 | 2. Get English legacy config files and put them in a dir: 11 | 12 | $ CONFIG_EN=... 13 | 14 | 3. Patch these files by hand (see parse_legacy_config.py for FIXME notes describing how). 15 | 16 | 4. Define (relative to this file): 17 | 18 | $ MARCMAP=../whelk-core/src/main/resources/marcmap.json 19 | 20 | 5. Run: 21 | 22 | $ python parse_legacy_config.py $CONFIG_EN/TagTable/Marc21 en $CONFIG_SV/TagTable/Marc21 sv > $MARCMAP 23 | 24 | 6. Download a FRBR CSV mapping: 25 | 26 | $ FRBR_CSV=... 27 | $ curl http://www.loc.gov/marc/marc-functional-analysis/source/FRBR_Web_Copy.txt -o $FRBR_CSV 28 | 29 | 7. Add frbr hints as entity annotations in the marcmap by running: 30 | 31 | $ python frbrize_marcmap.py $FRBR_CSV $MARCMAP > tmpout && mv tmpout $MARCMAP 32 | 33 | -------------------------------------------------------------------------------- /scripts/marc/describe_marcmap.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | def describe_marcmap(marcmap): 4 | for tag, field in sorted(marcmap['bib'].items()): 5 | if tag == 'fixprops': 6 | continue 7 | print tag,_id_or_label(field) 8 | for fixmap in field.get('fixmaps', ()): 9 | print " ", fixmap.get('label_en', "").replace('&', '') 10 | for col in fixmap['columns']: 11 | print " {0} ({1}+{2})".format(*[col.get(key) for key in 12 | ('propRef', 'offset', 'length')]) 13 | if 'subfield' in field: 14 | # TODO: ind is a dict, *value* has _id_or_label 15 | #inds = ", ".join(filter(None, (_id_or_label(field[ind]) 16 | # for ind in ['ind1', 'ind2'] if ind in field))) 17 | inds = ", ".join(filter(None, (" ".join(field[ind].keys()) 18 | for ind in ['ind1', 'ind2'] if ind in field))) 19 | if inds: 20 | print " ", inds 21 | for code, subfield in field['subfield'].items(): 22 | print " ", code, subfield['id'] 23 | print 24 | 25 | def _id_or_label(data): 26 | return data.get('id') or data.get('label_en') 27 | 28 | 29 | if __name__ == '__main__': 30 | from sys import argv, exit 31 | import os 32 | if len(argv) < 2: 33 | print "Usage: {0} MARCMAP".format(os.path.basename(argv[0])) 34 | exit() 35 | fpath = argv[1] 36 | with open(fpath) as f: 37 | describe_marcmap(json.load(f)) 38 | 39 | -------------------------------------------------------------------------------- /scripts/marc/frbrize_marcmap.py: -------------------------------------------------------------------------------- 1 | import re 2 | from collections import namedtuple, OrderedDict 3 | from itertools import starmap 4 | import csv 5 | 6 | from sys import stderr 7 | #import cgitb; cgitb.enable(format='text') 8 | def error(msg, *args): 9 | print >>stderr, msg.format(*args) 10 | 11 | 12 | class Item(namedtuple('Item', 13 | "f1 f2 rectype field fieldname subfield position name comment entity" 14 | " description f5 f6 f7 f8 f9 f10 f11 f13 f14 f15 f16 f17 f18 f19")): 15 | pass 16 | 17 | 18 | entity_map = { 19 | 'Corp.Body': 'CorporateBody', 20 | 'Manifest./Item': 'ManifestationOrItem', 21 | 'C/O/E/P': 'ConceptOrObjectOrEventOrPlace', 22 | 'Expresson': 'Expression', 23 | 'Person/Corp.': 'Agent', 24 | } 25 | 26 | rectype_map = { 27 | '006': {'Continuing': 'Serial'}, 28 | '007': { 29 | 'Electronic': 'Computer', 30 | 'NPG': 'NonprojectedGraphic', 31 | 'MP': 'MotionPicture', 32 | 'Music': 'NotatedMusic', 33 | 'RSI': 'RemoteSensingImage', 34 | 'SR': 'SoundRecording', 35 | }, 36 | '008': {'All': ''} 37 | } 38 | 39 | 40 | def get_items(fpath): 41 | with open(fpath, 'rb') as f: 42 | reader = csv.reader(f) 43 | for item in starmap(Item, reader): 44 | field = item.field 45 | fixmap = None 46 | if field == 'ldr': 47 | field = '000' 48 | elif field == 'dir': 49 | continue # TODO: ok? Directory is about low-level syntax parsning 50 | elif len(field) > 3 and field[3].isalpha(): 51 | field, rectype = field[0:3], field[3:] 52 | rectype = rectype_map.get(field, {}).get(rectype, rectype) 53 | fixmap = field + "_" + rectype 54 | else: 55 | fixmap = None 56 | entity = re.sub(r'\?|\+[EA]\d+|^n/a$', '', 57 | item.entity.replace('\x98', '')).title().replace(' ', '') 58 | entity = entity_map.get(entity, entity) 59 | item = item._replace( 60 | field=field, 61 | rectype=item.rectype.split('/'), 62 | position=item.position if item.position != 'n/a' else None, 63 | subfield=item.subfield if item.subfield != 'n/a' else None, 64 | entity=entity) 65 | item.fixmap = fixmap 66 | yield item 67 | 68 | def dump_entities(items): 69 | entities = set(item.entity for item in items) 70 | for it in entities: 71 | if it: 72 | print it 73 | 74 | def add_entities_to_marcmap(marcmap, items): 75 | for item in items: 76 | if not item.entity: 77 | continue 78 | if "BD" in item.rectype: 79 | recmap = marcmap['bib'] 80 | #elif "HD" in item.rectype 81 | # recmap = marcmap['holdings'] 82 | else: 83 | continue 84 | field = recmap.get(item.field) 85 | if not field: 86 | error("Unknown field: {0}", item.field) 87 | continue 88 | if item.subfield: 89 | try: 90 | field['subfield'][item.subfield]['entity'] = item.entity 91 | except KeyError: 92 | error("Unknown field: {0.field}, subfield: {0.subfield}", item) 93 | elif item.position: 94 | matchmap = None 95 | # TODO: just guessing about 01 and 02 96 | if item.position == '01': 97 | field['ind1_entity'] = item.entity 98 | elif item.position == '02': 99 | field['ind2_entity'] = item.entity 100 | elif item.fixmap: 101 | matchmap = item.fixmap.split('/')[0] 102 | elif item.field == '000': 103 | matchmap = '000_BibLeader' 104 | 105 | if matchmap and 'fixmaps' in field: 106 | 107 | for fixmap in field['fixmaps']: 108 | name = fixmap['name'] 109 | if name.endswith('s') and not matchmap.endswith('s'): 110 | name = name[0:-1] 111 | if name.startswith(matchmap): 112 | break 113 | else: 114 | error("Found no fixmap for field {0} matching {1} (entity: {2})", 115 | item.field, matchmap, item.entity) 116 | if '-' in item.position: 117 | item_start, item_stop = map(int, item.position.split('-')) 118 | # TODO: is this always information about repeated attributes? 119 | else: 120 | item_start, item_stop = [int(item.position)] * 2 121 | columns = fixmap['columns'] 122 | for col in columns: 123 | offset, length = col['offset'], col['length'] 124 | start, stop = offset, offset + length - 1 125 | if start >= item_start and stop <= item_stop: 126 | col['entity'] = item.entity 127 | else: 128 | field['entity'] = item.entity 129 | else: 130 | field['entity'] = item.entity 131 | 132 | 133 | if __name__ == '__main__': 134 | from sys import argv, stdout 135 | frbrcsv_path = argv[1] 136 | marcmap_path = argv[2] if len(argv) > 2 else None 137 | 138 | # Use CSV from: 139 | # 140 | items = get_items(frbrcsv_path) 141 | 142 | if marcmap_path == '-e': 143 | dump_entities(items) 144 | elif not marcmap_path: 145 | for item in items: 146 | print item.field, item.subfield or item.position, item.entity 147 | else: 148 | import json 149 | with open(marcmap_path) as f: 150 | marcmap = json.load(f, object_pairs_hook=OrderedDict) 151 | add_entities_to_marcmap(marcmap, items) 152 | json.dump(marcmap, stdout, indent=2) 153 | 154 | -------------------------------------------------------------------------------- /scripts/marc/marcmap-tokenmaps.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "propRefs": { 4 | "Förminskningsgrad (exakt) - högerjust med inledande nolla": "reductionRatioExactRightJustifiedWithLeadingZero", 5 | "Utgivningsland": "country", 6 | "Årtal/Tid (1)": "publishedYear", 7 | "Årtal/Tid (2)": "otherYear", 8 | "Årtal/Tid (1)": "publishedYear", 9 | "Årtal/Tid (2)": "otherYear", 10 | "Datum för posten": "date", 11 | "Språk": "language", 12 | "Tidpunkt för granskning": "timeOfExamination", 13 | "Posttyp": "typeOfRecord" 14 | }, 15 | 16 | "fixprops": { 17 | 18 | "catForm": { 19 | "c": "ISBDPunctuationOmitted" 20 | }, 21 | "characterCoding": { 22 | "a": "UCS-Unicode" 23 | }, 24 | "linked": { 25 | "a": "Set", 26 | "b": "PartWithIndependentTitle", 27 | "c": "PartWithDependentTitle", 28 | "r": "RecordHasLinks-Obsolete" 29 | }, 30 | 31 | "booksContents": { 32 | "5": "Calendar", 33 | "6": "ComicOrGraphicNovel", 34 | "y": "Yearbook" 35 | }, 36 | "booksItem": { 37 | "o": "Online", 38 | "q": "DirectElectronic", 39 | "s": "Electronic" 40 | }, 41 | 42 | "computerMaterial": { 43 | "a": "TapeCartridge", 44 | "b": "ChipCartridge", 45 | "c": "ComputerOpticalDiscCartridge", 46 | "d": "ComputerDisc", 47 | "e": "ComputerDiscCartridge", 48 | "f": "TapeCassette", 49 | "h": "TapeReel", 50 | "j": "MagneticDisk", 51 | "k": "ComputerCard", 52 | "m": "MagnetoOpticalDisc", 53 | "o": "OpticalDisc", 54 | "r": "OnlineResource", 55 | "u": "Unspecified", 56 | "z": "Other" 57 | }, 58 | "computerAntecedent": { 59 | "a": "FileReproducedFromOriginal", 60 | "b": "FileReproducedFromMicroform", 61 | "c": "FileReproducedFromElectronicResource", 62 | "d": "FileReproducedFromIntermediate", 63 | "m": "MixedSources", 64 | "n": "NotApplicable", 65 | "u": "Unknown" 66 | }, 67 | "computerColor": { 68 | "a": "OneColor", 69 | "b": "BlackAndWhite", 70 | "c": "Multicolored", 71 | "g": "GrayScale", 72 | "m": "MixedColors", 73 | "n": "NotApplicable", 74 | "u": "Unknown", 75 | "z": "Other" 76 | }, 77 | "computerCompression": { 78 | "a": "Uncompressed", 79 | "b": "LosslessCompression", 80 | "d": "LossyCompression", 81 | "m": "MixedCompression", 82 | "n": "NotApplicable", 83 | "u": "Unknown" 84 | }, 85 | "computerDimensions": { 86 | "a": "3,5_in", 87 | "e": "12_in", 88 | "g": "4,75_in_or_12_cm_(CD)", 89 | "i": "1,125_x_2,375_in", 90 | "j": "3,875_x_2,5_in", 91 | "o": "5,25_in", 92 | "v": "8_in", 93 | "n": "NotApplicable", 94 | "u": "Unknown", 95 | "z": "Other" 96 | }, 97 | "computerFileFormats": { 98 | "a": "OneFileFormat", 99 | "m": "MultipleFileFormats", 100 | "u": "Unknown" 101 | }, 102 | "computerImageBitDepth": { 103 | "---": "Unknown", 104 | "001~999": "TODO:value-in-digits", 105 | "mmm": "MultipleImageBitDepths", 106 | "nnn": "NotApplicable" 107 | }, 108 | "computerItem": "booksItem", 109 | "computerQATarget": { 110 | "a": "TestImagesEtcAbsent", 111 | "n": "NotApplicable", 112 | "p": "TestImagesEtcPresent", 113 | "u": "Unknown" 114 | }, 115 | "computerReformattingQuality": { 116 | "a": "AccessFormatQuality", 117 | "n": "NotApplicable", 118 | "p": "PreservationFormatQuality", 119 | "r": "ReplacementFormatQuality", 120 | "u": "Unknown" 121 | }, 122 | "computerSound": { 123 | "#": "NoSound", 124 | "a": "Sound", 125 | "u": "Unknown" 126 | }, 127 | 128 | "globeMedium": { 129 | "i": "Plastic", 130 | "l": "Vinyl", 131 | "n": "Vellum", 132 | "v": "Leather", 133 | "w": "Parchment" 134 | }, 135 | 136 | "mapMedium": "globeMedium", 137 | 138 | "mapsItem": "booksItem", 139 | 140 | "mapsProjection": { 141 | "bk": "Krovak", 142 | "bl": "CassiniSoldner" 143 | }, 144 | 145 | "microformMaterial": { 146 | "h": "MicrofilmSlip", 147 | "j": "MicrofilmRoll" 148 | }, 149 | 150 | "mixedItem": "booksItem", 151 | 152 | "motionPicMaterial": { 153 | "o": "FilmRoll" 154 | }, 155 | 156 | "musicComposition": { 157 | "fl": "Flamenco", 158 | "tl": "TeatroLirico", 159 | "vl": "Villancico", 160 | "za": "Zarzuela" 161 | }, 162 | 163 | "musicFormat": { 164 | "h": "ChoralScore", 165 | "i": "CondensedScore", 166 | "j": "PerformerConductorPart", 167 | "k": "VocalScore", 168 | "l": "CompleteMusicalNotationForScore" 169 | }, 170 | 171 | "musicItem": "booksItem", 172 | 173 | "nonProjMaterial": { 174 | "a": "ActivityCard", 175 | "k": "Poster", 176 | "p": "Postcard", 177 | "q": "Icon", 178 | "r": "Radiograph", 179 | "s": "StudyPrint", 180 | "v": "Photograph" 181 | }, 182 | 183 | "nonProjPrimary": "globeMedium", 184 | 185 | "nonProjSecondary": "globeMedium", 186 | 187 | "projGraphSupport": { 188 | "_": "NoMountSupport", 189 | "c": "Paper", 190 | "d": "Glass", 191 | "e": "Synthetic", 192 | "h": "Metal", 193 | "j": "MetalAndGlass", 194 | "k": "SyntheticAndGlass", 195 | "m": "MixedMountSupport", 196 | "u": "Unknown", 197 | "z": "Other" 198 | }, 199 | 200 | "serialsContents": "booksContents", 201 | 202 | "serialsISSN": { 203 | "f": "Sweden-Obsolete" 204 | }, 205 | 206 | "serialsItem": "booksItem", 207 | 208 | "serialsNature": { 209 | "6": "ComicOrGraphicNovel", 210 | "y": "Yearbook" 211 | }, 212 | 213 | "serialsOriginalItem": "booksItem", 214 | 215 | "tacBrailleMusic": { 216 | "z": "OtherBrailleFormat" 217 | }, 218 | 219 | "videoFormat": { 220 | "s": "BluRayDisc" 221 | }, 222 | 223 | "visualItem": "booksItem" 224 | 225 | } 226 | 227 | } 228 | -------------------------------------------------------------------------------- /scripts/marcframe-skeleton-from-bibframe.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | import json 3 | from rdflib import * 4 | 5 | BF = Namespace("http://bibframe.org/vocab/") 6 | ABS = Namespace("http://bibframe.org/model-abstract/") 7 | 8 | src = "/Users/nlm/Documents/rdfmodels/bibframe.ttl" 9 | 10 | g = Graph().parse(src, format="turtle") 11 | g.namespace_manager.bind("", BF) 12 | 13 | out = {} 14 | 15 | def add_value_or_list(d, k, v): 16 | current = d.get(k) 17 | if current == v: 18 | pass 19 | elif current: 20 | if not isinstance(current, list): 21 | d[k] = [current] 22 | d[k].append(v) 23 | else: 24 | d[k] = v 25 | 26 | for t in (RDFS.Class, ABS.BFProperty): 27 | for r in sorted(map(g.resource, g.subjects(RDF.type, t))): 28 | term = r.qname() 29 | marc = ",".join(r.objects(ABS.marcField)) 30 | if not marc: 31 | continue 32 | if t == RDFS.Class: 33 | if marc.startswith("Ldr"): 34 | # TODO: type determined by a combo of Ldr and some other fields (00[6-8]) + 33* 35 | continue 36 | tags = [s.strip()[0:3] for s in marc.split(",")] 37 | 38 | main_types = (BF.Work, BF.Instance) 39 | is_main_type = r.identifier in main_types or any(c.identifier in main_types 40 | for c in r.transitive_objects(RDFS.subClassOf)) 41 | entity_role = 'domainEntity' if is_main_type else 'rangeEntity' 42 | for tag in tags: 43 | field = out.setdefault(tag, {}) 44 | add_value_or_list(field, entity_role, term) 45 | else: 46 | if any(s in marc for s in ['=', '/', '??', 'if']): 47 | # TODO 48 | #print "Cannot parse condition:", marc 49 | continue 50 | if " " in marc: 51 | tags, subcodes = marc.split(" ", 1) 52 | subcodes = subcodes.split(",") 53 | else: 54 | tags, subcodes = marc, [] 55 | tags = [s for s in tags.split(",") if s and s.isdigit()] 56 | if all(len(c) == 3 for c in subcodes): 57 | tags += subcodes 58 | subcodes = [] 59 | ranges = list(r.objects(RDFS.range)) 60 | objranges = [rng for rng in ranges if rng.identifier.startswith(BF)] 61 | for tag in sorted(tags): 62 | field = out.setdefault(tag, {}) 63 | if subcodes: 64 | for code in sorted(subcodes): 65 | if code[0] == '$': 66 | code = code[1:] 67 | if code in "/+" or len(code) > 1: 68 | # TODO 69 | #print "unhandled:", subcodes 70 | continue 71 | subf = field.setdefault('$'+ code, {}) 72 | add_value_or_list(subf, 'id', term) 73 | if not ranges or objranges: 74 | add_value_or_list(field, 'link', term) 75 | if objranges: 76 | add_value_or_list(field, 'rangeEntity', objranges[0].qname()) 77 | 78 | def sortdict(o): 79 | if not isinstance(o, dict): 80 | return o 81 | return OrderedDict((k, sortdict(o[k])) for k in sorted(o)) 82 | 83 | out = sortdict(out) 84 | 85 | print json.dumps(out, 86 | indent=2, 87 | ensure_ascii=False, 88 | separators=(',', ': ') 89 | ).encode('utf-8') 90 | -------------------------------------------------------------------------------- /scripts/misc/convert-marcxml-to-bibframe.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SAXON_JAR=$1 3 | QUERY=$2 4 | SOURCE=$(pwd)/$3 5 | 6 | BASE=http://libris.kb.se/bib/ 7 | java -cp $SAXON_JAR net.sf.saxon.Query $QUERY marcxmluri=$SOURCE baseuri=$BASE serialization=rdfxml 8 | -------------------------------------------------------------------------------- /scripts/misc/jsontr.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import re 3 | from collections import OrderedDict 4 | import json 5 | 6 | 7 | def camelize(data, source, dest=None): 8 | dest = dest or source 9 | for o in data.values(): 10 | if source not in o: continue 11 | v = o[source] 12 | o[dest] = v[0].lower() + re.sub(r"\W", "", v.title()[1:]) 13 | return data 14 | 15 | def sort_keys(o): 16 | out = OrderedDict() 17 | for key in sorted(o.keys()): 18 | out[key] = o[key] 19 | return out 20 | 21 | def to_list(o, keykey='key'): 22 | l = [] 23 | for k, v in o.items(): 24 | if isinstance(v, dict) and keykey not in v: 25 | v2 = OrderedDict() 26 | v2[keykey] = k 27 | v2.update(v) 28 | l.append(v2) 29 | else: 30 | l.append(v) 31 | return l 32 | 33 | def to_dict(data, keykey, keep=None): 34 | return OrderedDict( 35 | (v[keykey] if keep == 'keep' else v.pop(keykey), v) 36 | for v in data) 37 | 38 | def objectify(data, key): 39 | for k, v in data.items(): 40 | data[k] = {key: v} 41 | return data 42 | 43 | if __name__ == '__main__': 44 | import sys 45 | 46 | tr = vars()[sys.argv[1]] 47 | 48 | data = json.load(sys.stdin, object_pairs_hook=OrderedDict) 49 | data = tr(data, *sys.argv[2:]) 50 | 51 | print json.dumps(data, 52 | indent=2, 53 | ensure_ascii=False, 54 | separators=(',', ': ') 55 | ).encode('utf-8') 56 | -------------------------------------------------------------------------------- /scripts/misc/mk-graph-data.py: -------------------------------------------------------------------------------- 1 | import json 2 | from rdflib import * 3 | from rdflib.util import guess_format 4 | 5 | 6 | VANN = Namespace("http://purl.org/vocab/vann/") 7 | 8 | 9 | def to_graph(paths): 10 | g = Graph() 11 | for fpath in paths: 12 | g.parse(fpath, format=guess_format(fpath)) 13 | return g 14 | 15 | def get_name(r, lang): 16 | for label in []:#r.objects(RDFS.label): 17 | name = unicode(label) 18 | if label.language == lang: 19 | break 20 | else: 21 | name = r.qname() 22 | return name 23 | 24 | def in_vocab(r, vocab): 25 | if not vocab.endswith(':'): 26 | r.graph.bind('', URIRef(vocab)) 27 | if isinstance(r.identifier, BNode): 28 | return False 29 | return ':' not in r.qname() or r.qname().startswith(vocab) 30 | 31 | 32 | def make_vocab_tree(g, lang, vocab): 33 | topclasses = [] 34 | for rclass in g.resource(OWL.Class).subjects(RDF.type): 35 | if any(pc for pc in rclass.objects(RDFS.subClassOf) if in_vocab(pc, vocab)): 36 | continue 37 | topclasses.append(tree_node(rclass, lang)) 38 | return { 39 | 'name': 'Thing', 40 | 'children': topclasses 41 | } 42 | 43 | def tree_node(rclass, lang): 44 | name = get_name(rclass, lang) 45 | children = [tree_node(sc, lang) for sc in rclass.subjects(RDFS.subClassOf)] 46 | node = {'name': name} 47 | if children: 48 | node['children'] = children 49 | return node 50 | 51 | 52 | def make_vocab_graph(g, lang, vocab): 53 | nodes = [] 54 | classes = set(g.resource(r) for r, o in g.subject_objects(RDF.type) 55 | if o in {RDFS.Class, OWL.Class}) 56 | for rclass in classes: 57 | if not in_vocab(rclass, vocab): 58 | continue 59 | name = get_name(rclass, lang) 60 | child_names = [get_name(sc, lang) for sc in rclass.subjects(RDFS.subClassOf)] 61 | node = {'name': name, 'children': child_names} 62 | node['termgroups'] = map(unicode, rclass.objects(VANN.termGroup)) 63 | nodes.append(node) 64 | 65 | return {'nodes': sorted(nodes, key=lambda node: node['name'])} 66 | 67 | 68 | if __name__ == '__main__': 69 | import sys 70 | args = sys.argv[1:] 71 | kind = args.pop(0) 72 | lang = 'sv' 73 | vocab = args.pop(0) 74 | g = to_graph(args) 75 | if kind == 'tree': 76 | data = make_vocab_tree(g, lang, vocab) 77 | else: 78 | data = make_vocab_graph(g, lang, vocab) 79 | print json.dumps(data, sort_keys=True, ensure_ascii=False, 80 | indent=2, separators=(',', ': ')).encode('utf-8') 81 | -------------------------------------------------------------------------------- /scripts/vocab-update-cleanup.rq: -------------------------------------------------------------------------------- 1 | PREFIX owl: 2 | PREFIX dbpo: 3 | PREFIX sdo: 4 | PREFIX : 5 | 6 | DELETE DATA { 7 | 8 | :influencedBy owl:equivalentProperty dbpo:influencedBy . 9 | 10 | :title owl:equivalentProperty sdo:title . 11 | :sameAs owl:equivalentProperty sdo:sameAs . 12 | :comment owl:equivalentProperty sdo:comment . 13 | :source owl:equivalentProperty sdo:source . 14 | 15 | :Unspecified a owl:Class; rdfs:subClassOf :Product . 16 | 17 | } 18 | -------------------------------------------------------------------------------- /scripts/vocab-update-unstable.rq: -------------------------------------------------------------------------------- 1 | PREFIX owl: 2 | PREFIX vs: 3 | 4 | 5 | INSERT { 6 | ?term vs:term_status "unstable" 7 | } WHERE { 8 | { 9 | ?term a owl:Class 10 | FILTER NOT EXISTS { ?term owl:equivalentClass ?eqc } 11 | } UNION { 12 | { 13 | ?term a owl:ObjectProperty 14 | } UNION { 15 | ?term a owl:DatatypeProperty 16 | } 17 | FILTER NOT EXISTS { ?term owl:equivalentProperty ?eqp } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /source/app/help.jsonld: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 4 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 5 | "dc": "http://purl.org/dc/terms/", 6 | "skos": "http://www.w3.org/2004/02/skos/core#", 7 | "app": "http://kat.libris.kb.se/def/application#", 8 | "index": {"@container": "@index", "@id": "@graph"}, 9 | "label": {"@id": "rdfs:label", "@language": "sv"}, 10 | "comment": {"@id": "rdfs:comment", "@language": "sv"}, 11 | "example": {"@id": "skos:example"}, 12 | "descriptionHTML": {"@id": "dc:description", "@type": "rdf:HTML"} 13 | }, 14 | "index": { 15 | "app:restrictedURI": { 16 | "@id": "app:restrictedURI", 17 | "label": "URI för åtkomstbegränsade resurser", 18 | "descriptionHTML": "

Endast för fulltexter och liknande där åtkomsten är begränsad till bibliotekets användare, t.ex. via proxy eller IP-nummerfiltrering.

Klicka på ”Visa fler fält” om URI:n (webbadressen) behöver kompletteras med anmärkning etc.
Exempel
URI
http://ezproxy.its.uu.se/login?url=http://gateway.proquest.com/openurl?url_ver=Z39.88-2004&res_dat=xri:eurobo:&rft_dat=xri:eurobo:image:den-kbd-pil-130018150272-001
Publik anmärkning
Tillgänglig för användare inom Uppsala universitet

Observera att URI (webbadress) till fritt tillgängliga fulltexter ska anges i den bibliografiska posten (registreras tills vidare endast via Voyager)

" 19 | }, 20 | "app:offers": { 21 | "@id": "app:offers", 22 | "label": "Placering", 23 | "descriptionHTML": "

Bibliotekets sigel är obligatorisk och förifylld. Klicka på ”Spara bestånd” om du inte ska lägga till fler uppgifter om placering etc.

För att lägga till fler uppgifter, gå till fälten Klassifikation (hylla) och eller Löpnummer. Placeringsuppgift registreras enligt praxis vid ditt bibliotek.

Klassifikationskoden kan kompletteras med ytterligare placeringsuppgifter, t.ex. titel- eller författarord. Klicka på ”Visa fler fält” om respektive uppställningsled behöver särskiljas eller anges i olika fält.

Klicka på ”Visa fler fält” om uppgiften behöver kompletteras med anmärkning.

Klicka på ”Lägg till bestånd för seriella resurser” om uppgiften behöver kompletteras med beståndsanmärkning.

För eventuella exemplar uppställda på annan hylla (motsvarande), eller med med avvikande tillgänglighet, eller uppställda enligt annan placering, eller med annat löpnummer, eller uppställda under annat ord / annan fras, eller uppställda på annan avdelning (motsvarande), klicka på ”Lägg till placeringsfält”.

" 24 | }, 25 | "app:suggestions": { 26 | "comment": "Klicka på lämpligt förslag till höger om inmatningsfältet." 27 | }, 28 | "app:targetScopeOfMaterial": { 29 | "@id": "app:targetScopeOfMaterial", 30 | "label": "Del av materialet som avses", 31 | "comment": "Kan användas för att förtydliga att URI:n (webbadressen) bara avser delar av materialet, t.ex. en innehållsförteckning (table of contents)." 32 | }, 33 | "app:workExampleSomeProducts": { 34 | "@id": "app:workExampleSomeProducts", 35 | "label": "Volymer, häften, nummer etc.", 36 | "comment": "Ange de volymer, häften, nummer etc. som biblioteket har av tidskriften (motsvarande).", 37 | "example": ["1-10", "1-25(1952-1959), 1960-"] 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /source/bibdb/terms.ttl: -------------------------------------------------------------------------------- 1 | prefix owl: 2 | prefix rdfs: 3 | prefix skos: 4 | prefix kbv: 5 | prefix idkbse_bibdb: 6 | prefix : 7 | 8 | :Registrant a skos:Concept ; 9 | skos:prefLabel "Registrant"@en, "Registrerande"@sv ; 10 | rdfs:comment "Bibliotek i Librissamarbetet som skapar och underhåller metadata i den gemensamma datamängden."@sv . 11 | 12 | 13 | # https://git.kb.se/libris/bibdb/-/blob/develop/libdb/models.py#L262 14 | :natbib a idkbse_bibdb:LibraryType ; 15 | skos:notation "natbib" ; 16 | rdfs:label "Nationalbibliotek"@sv . 17 | 18 | :folkbib a idkbse_bibdb:LibraryType ; 19 | skos:notation "folkbib" ; 20 | rdfs:label "Folkbibliotek"@sv . 21 | 22 | :folkskolbib a idkbse_bibdb:LibraryType ; 23 | skos:notation "folkskolbib" ; 24 | rdfs:label "Folk- och skolbibliotek"@sv . 25 | 26 | :specbib a idkbse_bibdb:LibraryType ; 27 | skos:notation "specbib" ; 28 | rdfs:label "Specialbibliotek"@sv . 29 | 30 | :sjukbib a idkbse_bibdb:LibraryType ; 31 | skos:notation "sjukbib" ; 32 | rdfs:label "Sjukhusbibliotek"@sv . 33 | 34 | :myndbib a idkbse_bibdb:LibraryType ; 35 | skos:notation "myndbib" ; 36 | rdfs:label "Myndighetsbibliotek"@sv . 37 | 38 | :busbib a idkbse_bibdb:LibraryType ; 39 | skos:notation "busbib" ; 40 | rdfs:label "Företagsbibliotek"@sv . 41 | 42 | :musbib a idkbse_bibdb:LibraryType ; 43 | skos:notation "musbib" ; 44 | rdfs:label "Arkiv och museibibliotek"@sv . 45 | 46 | :muskom a idkbse_bibdb:LibraryType ; 47 | skos:notation "muskom" ; 48 | rdfs:label "Kommunalt museibibliotek"@sv . 49 | 50 | :muslan a idkbse_bibdb:LibraryType ; 51 | skos:notation "muslan" ; 52 | rdfs:label "Länsmuseibibliotek"@sv . 53 | 54 | :musstat a idkbse_bibdb:LibraryType ; 55 | skos:notation "musstat" ; 56 | rdfs:label "Statligt museibibliotek"@sv . 57 | 58 | :skolbib a idkbse_bibdb:LibraryType ; 59 | skos:notation "skolbib" ; 60 | rdfs:label "Skolbibliotek"@sv . 61 | 62 | :friskol a idkbse_bibdb:LibraryType ; 63 | skos:notation "friskol" ; 64 | rdfs:label "Friskolebibliotek"@sv . 65 | 66 | :gymbib a idkbse_bibdb:LibraryType ; 67 | skos:notation "gymbib" ; 68 | rdfs:label "Gymnasiebibliotek"@sv . 69 | 70 | :frisgym a idkbse_bibdb:LibraryType ; 71 | skos:notation "frisgym" ; 72 | rdfs:label "Friskolegymnasiebibliotek"@sv . 73 | 74 | :statskol a idkbse_bibdb:LibraryType ; 75 | skos:notation "statskol" ; 76 | rdfs:label "Statligt skolbibliotek"@sv . 77 | 78 | :vuxbib a idkbse_bibdb:LibraryType ; 79 | skos:notation "vuxbib" ; 80 | rdfs:label "Bibliotek på komvux/lärcentra"@sv . 81 | 82 | :univbib a idkbse_bibdb:LibraryType ; 83 | skos:notation "univbib" ; 84 | rdfs:label "Universitets- och högskolebibliotek"@sv . 85 | 86 | :folkhogbib a idkbse_bibdb:LibraryType ; 87 | skos:notation "folkhogbib" ; 88 | rdfs:label "Folkhögskolebibliotek"@sv . 89 | 90 | :ovrbib a idkbse_bibdb:LibraryType ; 91 | skos:notation "ovrbib" ; 92 | rdfs:label "Övriga bibliotek"@sv . 93 | 94 | # "typ av folkbibliotek" 95 | # https://git.kb.se/libris/bibdb/-/blob/develop/libdb/models.py#L287 96 | :kommunbib a idkbse_bibdb:LibraryType ; 97 | skos:notation "kommunbib" ; 98 | rdfs:label "Kommunbibliotek"@sv . 99 | 100 | :lanecentral a idkbse_bibdb:LibraryType ; 101 | skos:notation "lanecentral" ; 102 | rdfs:label "Lånecentral"@sv . 103 | 104 | :lansbib a idkbse_bibdb:LibraryType ; 105 | skos:notation "lansbib" ; 106 | rdfs:label "Länsbibliotek"@sv . -------------------------------------------------------------------------------- /source/bibliographies.jsonld: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "@vocab": "http://libris.kb.se/def/terms#", 4 | "bibliographyByNotation": {"@id": "@graph", "@container": "@index"}, 5 | "label": {"@language": "sv"} 6 | }, 7 | "bibliographyByNotation": { 8 | "BULB": { 9 | "notation": "BULB", 10 | "label": "BULB - Barn- och ungdomslitteraturbibliografin" 11 | }, 12 | "NB": { 13 | "notation": "NB", 14 | "label": "Nationalbibliografin - Böcker, Periodica, Kartor" 15 | }, 16 | "SEE": { 17 | "notation": "SEE", 18 | "label": "Seelig (bokdistributör)" 19 | }, 20 | "SLB": { 21 | "notation": "SLB", 22 | "label": "Svensk litteraturvetenskaplig bibliografi" 23 | }, 24 | "KVIN": { 25 | "notation": "KVIN", 26 | "label": "KVINNSAM" 27 | }, 28 | "SHB": { 29 | "notation": "SHB", 30 | "label": "Svensk historisk bibliografi" 31 | }, 32 | "SKvB": { 33 | "notation": "SKvB", 34 | "label": "Svensk konstvetenskaplig bibliografi" 35 | }, 36 | "SOT": { 37 | "notation": "SOT", 38 | "label": "Svenskt offentligt tryck -1833" 39 | }, 40 | "SMHB": { 41 | "notation": "SMHB", 42 | "label": "Svensk musikhistorisk bibliografi" 43 | }, 44 | "DALA": { 45 | "notation": "DALA", 46 | "label": "Dalabibliografin" 47 | }, 48 | "SUEC": { 49 | "notation": "SUEC", 50 | "label": "Suecana extranea" 51 | }, 52 | "SAH": { 53 | "notation": "SAH", 54 | "label": "Suecia Antiqua et Hodierna" 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /source/cc-licenses-header.txt: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # IMPORTANT: generated data (see ../Makefile)! 3 | # 4 | # Custom selection of license metadata from Creative Commons 5 | # (). 6 | # 7 | # According to the metadata, the licenses themselves are licensed under 8 | # . Since this URI designates the 9 | # *web page*, presumably this metadata is also published under that license. 10 | # 11 | # However, this is contradicted by the statements in their corresponding "legal 12 | # code" documents (e.g. at 13 | # ), which reads: 14 | # 15 | # > The text of the Creative Commons public licenses is dedicated to the public 16 | # > domain under the CC0 Public Domain Dedication. 17 | # 18 | # We have contacted Creative Commons to get a clarification of this. According 19 | # to them: 20 | # 21 | # > The legal text of the CC Licenses is dedicated to the public domain with 22 | # > CC0. Therefore the meta-data tags are also available under those terms. 23 | # 24 | # This is very accommodating, and facilitates any use and re-use of this 25 | # metadata. We leave this notice in place to be clear. 26 | ############################################################################### 27 | -------------------------------------------------------------------------------- /source/changecategories.ttl: -------------------------------------------------------------------------------- 1 | prefix skos: 2 | prefix : 3 | 4 | base 5 | 6 | a :ChangeCategory ; 7 | skos:prefLabel "Primary contribution"@en, "Primär medverkan"@sv ; 8 | skos:altLabel "Change of primary contribution"@en, "Ändring av primär medverkan"@sv . 9 | 10 | a :ChangeCategory ; 11 | skos:prefLabel "Main title"@en, "Huvudtitel"@sv ; 12 | skos:altLabel "Change of main title"@en, "Ändring av huvudtitel"@sv . 13 | 14 | a :ChangeCategory ; 15 | skos:prefLabel "Key title"@en, "Nyckeltitel"@sv ; 16 | skos:altLabel "Change of key title"@en, "Ändring av nyckeltitel"@sv . 17 | 18 | a :ChangeCategory ; 19 | skos:prefLabel "Agent as subject"@en, "Agent som ämne"@sv ; 20 | skos:altLabel "Change of agent as subject"@en, "Ändring av agent som ämne"@sv . 21 | 22 | a :ChangeCategory ; 23 | skos:prefLabel "Intended audience"@en, "Målgrupp"@sv ; 24 | skos:altLabel "Change of intended audience"@en, "Ändring av målgrupp"@sv . 25 | 26 | a :ChangeCategory ; 27 | skos:prefLabel "DDC classification"@en, "DDK-klassifikation"@sv ; 28 | skos:altLabel "Change of DDC classification"@en, "Ändring av DDK-klassifikation"@sv . 29 | 30 | a :ChangeCategory ; 31 | skos:prefLabel "SAB classification"@en, "SAB-klassifikation"@sv ; 32 | skos:altLabel "Change of SAB classification"@en, "Ändring av SAB-klassifikation"@sv . 33 | 34 | a :ChangeCategory ; 35 | skos:prefLabel "Serial relationships"@en, "Serierelation"@sv ; 36 | skos:altLabel "Change of serial relationships"@en, "Ändring av serierelation"@sv . 37 | 38 | a :ChangeCategory ; 39 | skos:prefLabel "Discontinued serial resource"@en, "Avslutad seriell resurs"@sv . 40 | 41 | a :ChangeCategory ; 42 | skos:prefLabel "Change in agent (too many affected instances for individual notifications)"@en, "Ändring av agent (för många påverkade instanser för att kunna meddelas var för sig)"@sv . 43 | -------------------------------------------------------------------------------- /source/construct-cc-license-data.rq: -------------------------------------------------------------------------------- 1 | PREFIX cc: 2 | PREFIX dct: 3 | PREFIX frbr: 4 | PREFIX owl: 5 | PREFIX xhv: 6 | 7 | CONSTRUCT { 8 | ?secure a cc:License ; 9 | owl:sameAs ?license ; 10 | ?p ?o ; 11 | dct:identifier ?identifier ; 12 | frbr:translation ?translation . 13 | } WHERE { 14 | GRAPH ?g { 15 | ?license a cc:License . 16 | OPTIONAL { ?license dct:identifier ?id } 17 | BIND(REPLACE(STR(?id), "^\\s*\\(|\\)\\s*$", "") AS ?identifier) 18 | } 19 | VALUES (?p) { 20 | (dct:creator) (dct:title) (cc:permits) (cc:requires) (cc:prohibits) (xhv:license) 21 | } 22 | ?license ?p ?o . 23 | OPTIONAL { 24 | ?license frbr:translation ?translation . 25 | FILTER(?translation = ?g) 26 | } 27 | BIND(IF(!BOUND(?translation) && ?g != ?license, ?g, ?nothing) AS ?same) 28 | BIND(IF(STRSTARTS(STR(?license), "http://"), 29 | IRI(CONCAT("https://", STRAFTER((STR(?license)), "http://"))), 30 | ?license) AS ?secure) 31 | } 32 | -------------------------------------------------------------------------------- /source/construct-countries.rq: -------------------------------------------------------------------------------- 1 | prefix : 2 | prefix madsrdf: 3 | prefix xsd: 4 | prefix rdfs: 5 | prefix skos: 6 | prefix dc: 7 | 8 | construct { 9 | ?s ?p ?o ; 10 | skos:exactMatch ?loc ; 11 | skos:prefLabel ?prefLabel . 12 | 13 | } where { 14 | graph { 15 | { 16 | { 17 | ?s ?p ?o . 18 | } union { 19 | ?s skos:notation ?notation . 20 | graph { 21 | ?loc madsrdf:authoritativeLabel|skos:prefLabel ?prefLabel . 22 | filter strends(str(?loc), concat('/', ?notation)) 23 | } 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /source/construct-languages-iso639-1.rq: -------------------------------------------------------------------------------- 1 | prefix rdfs: 2 | prefix xsd: 3 | prefix skos: 4 | prefix madsrdf: 5 | prefix dc: 6 | prefix iso639_1: 7 | prefix kbv: 8 | prefix langtag: 9 | 10 | construct { 11 | ?s ?p ?o ; 12 | owl:sameAs ?bcp47alias ; 13 | skos:notation ?langCodeShort, ?langTag ; 14 | skos:closeMatch ?locVariant . 15 | } where { 16 | graph { 17 | ?s ?p ?o . 18 | optional { 19 | ?s skos:prefLabel ?prefLabel . 20 | graph { 21 | ?locVariant madsrdf:authoritativeLabel|skos:prefLabel ?prefLabel 22 | bind(substr(str(?locVariant), strlen(str(iso639_1:)) + 1) as ?shortcode) 23 | bind(strdt(?shortcode, kbv:ISO639-1) as ?langCodeShort) 24 | } 25 | } 26 | optional { 27 | ?s skos:notation ?iso639_2 . 28 | filter(datatype(?iso639_2) = kbv:ISO639-2) 29 | } 30 | optional { 31 | ?s skos:notation ?iso639_3 . 32 | filter(datatype(?iso639_3) = kbv:ISO639-3) 33 | } 34 | bind(str( 35 | coalesce(?langCodeShort, 36 | coalesce(?iso639_2, ?iso639_3)) 37 | ) as ?langTagStr) 38 | bind(strdt(?langTagStr, kbv:BCP47) as ?langTag) 39 | bind(IRI(concat(str(langtag:), ?langTagStr)) as ?bcp47alias) 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /source/construct-languages-iso639-2.rq: -------------------------------------------------------------------------------- 1 | prefix rdfs: 2 | prefix xsd: 3 | prefix skos: 4 | prefix madsrdf: 5 | prefix dc: 6 | prefix iso639_2: 7 | prefix kbv: 8 | 9 | construct { 10 | ?s ?p ?o ; 11 | skos:prefLabel ?prefLabel ; 12 | skos:altLabel ?altLabel ; 13 | skos:exactMatch ?locLang ; 14 | } where { 15 | graph { 16 | { 17 | ?s ?p ?o . 18 | } union { 19 | ?s skos:notation ?locCode . 20 | filter( 21 | datatype(?locCode) = kbv:ISO639-2-T || 22 | datatype(?locCode) = xsd:string 23 | ) 24 | bind(iri(concat(str(iso639_2:), str(?locCode))) as ?locLang) 25 | graph { 26 | { 27 | filter(!contains(?prefLabel, "|")) 28 | ?locLang madsrdf:authoritativeLabel|skos:prefLabel ?prefLabel 29 | } union { 30 | filter(contains(?prefLabels, "|")) 31 | ?locLang madsrdf:authoritativeLabel|skos:prefLabel ?prefLabels 32 | bind(replace(?prefLabels, " *\\|.*$", "") as ?prefLabel) 33 | bind(replace(?prefLabels, "^[^|]+\\| *", "") as ?altLabels) 34 | values ?n { 1 2 3 4 5 6 7 } 35 | bind(concat("^([^|]+\\|){", str(?n) ,"} *") as ?skipN) 36 | bind(replace(replace(?altLabels, ?skipN, ""), " *\\|.*$", "") as ?altLabel) 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /source/construct-materials.rq: -------------------------------------------------------------------------------- 1 | prefix skos: 2 | 3 | construct { 4 | ?s ?p ?o ; 5 | skos:prefLabel ?prefLabel ; 6 | skos:definition ?definition . 7 | } where { 8 | graph { 9 | ?s ?p ?o . 10 | optional { 11 | ?s skos:exactMatch ?aat . 12 | graph { 13 | ?aat skos:prefLabel ?aatLabel . 14 | } 15 | } 16 | optional { 17 | ?s skos:exactMatch ?rda . 18 | graph { 19 | ?rda skos:prefLabel ?rdaLabel ; 20 | skos:definition ?definition . 21 | } 22 | } 23 | bind(coalesce(?rdaLabel, ?aatLabel) as ?prefLabel) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/construct-musnotationsterms.rq: -------------------------------------------------------------------------------- 1 | prefix skos: 2 | 3 | construct { 4 | ?s ?p ?o ; 5 | skos:prefLabel ?prefLabel ; 6 | skos:definition ?definition . 7 | } where { 8 | graph { 9 | ?s ?p ?o ; skos:exactMatch ?rda . 10 | optional { 11 | graph { 12 | ?rda skos:prefLabel ?prefLabel ; 13 | skos:definition ?definition . 14 | filter(lang(?prefLabel) != 'sv') 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/construct-rda-terms.rq: -------------------------------------------------------------------------------- 1 | prefix madsrdf: 2 | prefix rdfs: 3 | prefix owl: 4 | prefix skos: 5 | 6 | construct { 7 | 8 | ?s ?p ?o ; 9 | rdfs:label ?lcLabel ; 10 | skos:prefLabel ?rdapreflabel ; 11 | skos:definition ?rdadefinition . 12 | 13 | } where { 14 | { 15 | graph { 16 | ?s ?p ?o . 17 | } 18 | } union { 19 | ?s skos:exactMatch ?lc . 20 | filter(strstarts(str(?lc), 'http://id.loc.gov/vocabulary/')) 21 | ?lc madsrdf:authoritativeLabel|skos:prefLabel ?lcLabel . 22 | } union { 23 | ?s skos:exactMatch ?rda . 24 | filter(strstarts(str(?rda), 'http://rdaregistry.info/termList/')) 25 | ?rda skos:prefLabel ?rdapreflabel ; 26 | skos:definition ?rdadefinition . 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/construct-relators.rq: -------------------------------------------------------------------------------- 1 | prefix rdfs: 2 | prefix owl: 3 | prefix skos: 4 | prefix bf2: 5 | prefix madsrdf: 6 | prefix : 7 | 8 | construct { 9 | ?s ?p ?o ; 10 | skos:prefLabel ?enLabel, ?fiLabel, ?deLabel, ?frLabel . 11 | 12 | } where { 13 | graph { 14 | { 15 | ?s ?p ?o . 16 | } union { 17 | ?s skos:exactMatch ?lc . 18 | graph { 19 | ?lc madsrdf:authoritativeLabel|skos:prefLabel ?lcLabel . 20 | bind(strlang(?lcLabel, 'en') as ?enLabel) 21 | } 22 | } union { 23 | ?s skos:exactMatch ?mts . 24 | graph { 25 | ?mts skos:prefLabel ?fiLabel . 26 | filter(lang(?fiLabel) = 'fi') 27 | } 28 | } union { 29 | ?s skos:exactMatch ?dnb . 30 | graph { 31 | ?dnb rdfs:label ?deLabel . 32 | filter(lang(?deLabel) = 'de') 33 | } 34 | } union { 35 | ?s skos:exactMatch ?bnf . 36 | graph { 37 | ?bnf skos:prefLabel ?frLabel . 38 | filter(lang(?frLabel) = 'fr') 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /source/construct-tacnotationterms.rq: -------------------------------------------------------------------------------- 1 | prefix skos: 2 | 3 | construct { 4 | ?s ?p ?o ; 5 | skos:prefLabel ?prefLabel ; 6 | skos:definition ?definition . 7 | } where { 8 | graph { 9 | ?s ?p ?o ; skos:exactMatch ?rda . 10 | optional { 11 | graph { 12 | ?rda skos:prefLabel ?prefLabel ; 13 | skos:definition ?definition . 14 | filter(lang(?prefLabel) != 'sv') 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /source/containers.ttl: -------------------------------------------------------------------------------- 1 | @prefix : . 2 | 3 | a :EntityContainer ; 4 | :membershipResource ; 5 | :isMemberOfRelation :inScheme ; 6 | :memberClass :Topic, :Temporal, :Geographic, :ComplexSubject, :TopicSubdivision ; 7 | :slugProperty :prefLabel ; 8 | :administeredBy . 9 | 10 | a :EntityContainer ; 11 | :membershipResource ; 12 | :isMemberOfRelation :inScheme ; 13 | :memberClass :GenreForm ; 14 | :slugProperty :prefLabel ; 15 | :administeredBy . 16 | 17 | a :EntityContainer ; 18 | :membershipResource ; 19 | :isMemberOfRelation :inScheme ; 20 | :memberClass :Topic, :Temporal ; 21 | :slugProperty :prefLabel ; 22 | :administeredBy , 23 | . 24 | 25 | a :EntityContainer ; 26 | :membershipResource ; 27 | :isMemberOfRelation :inScheme ; 28 | :memberClass :GenreForm ; 29 | :slugProperty :prefLabel ; 30 | :administeredBy , 31 | . 32 | 33 | a :EntityContainer ; 34 | :membershipResource ; 35 | :isMemberOfRelation :inScheme ; 36 | :memberClass :GenreForm ; 37 | :slugProperty :prefLabel ; 38 | :administeredBy . 39 | 40 | a :EntityContainer ; 41 | :membershipResource ; 42 | :isMemberOfRelation :inScheme ; 43 | :memberClass :Classification ; 44 | :slugProperty :code ; 45 | :administeredBy . 46 | 47 | # other existing systems: 48 | 49 | a :EntityContainer ; 50 | :membershipResource ; 51 | :isMemberOfRelation :inScheme ; 52 | :memberClass :Topic, :Temporal, :Geographic, :ComplexSubject, :TopicSubdivision ; 53 | :slugProperty :prefLabel ; 54 | :administeredBy . 55 | 56 | a :EntityContainer ; 57 | :membershipResource ; 58 | :isMemberOfRelation :inScheme ; 59 | :memberClass :Topic, :Temporal, :Geographic, :ComplexSubject, :TopicSubdivision ; 60 | :slugProperty :prefLabel ; 61 | :administeredBy . 62 | -------------------------------------------------------------------------------- /source/datasets/bibdb.ttl: -------------------------------------------------------------------------------- 1 | prefix xsd: 2 | prefix : 3 | base 4 | 5 | # IMPORTANT: these are currently self-described (the dataset description is 6 | # part of the dataset itself). If ever deleted and dataset items are remade 7 | # into "record-free" items, reloading the dataset will fail unless it is 8 | # forced, and if so it needs the descriptions below to compute the same 9 | # xl-id:s!) 10 | 11 | a :Dataset ; 12 | :created "2019-03-14T15:00:00.000Z"^^xsd:dateTime ; 13 | :dataSource ; 14 | :license . 15 | 16 | a :Dataset ; 17 | :created "2019-03-14T15:31:17.000Z"^^xsd:dateTime ; 18 | :uriSpace "https://libris.kb.se/library/" ; 19 | :isPartOf . 20 | 21 | a :Dataset ; 22 | :created "2019-03-14T19:32:20.000Z"^^xsd:dateTime ; 23 | :uriSpace "https://libris.kb.se/library/" ; 24 | :isPartOf . 25 | -------------------------------------------------------------------------------- /source/datasets/external.ttl: -------------------------------------------------------------------------------- 1 | prefix xsd: 2 | prefix : 3 | 4 | base 5 | 6 | a :Dataset ; 7 | :prefLabel "Dataset med QLIT-termer"@sv, "Dataset of QLIT terms"@en ; 8 | :uriSpace "https://queerlit.dh.gu.se/qlit/" ; 9 | :uriRegexPattern "https://queerlit\\.dh\\.gu\\.se/qlit/v1.*" ; 10 | :created "2022-05-19T08:12:00Z"^^xsd:dateTime ; 11 | :sourceData ; 12 | :datasetClass [ :broadMatch :Concept ; :closeMatch :Topic ] . 13 | 14 | a :Dataset ; 15 | :created "2022-10-13T13:25:01Z"^^xsd:dateTime ; 16 | :uriSpace "https://creativecommons.org/" ; 17 | :uriRegexPattern "https://creativecommons\\.org/.*" ; 18 | :sourceData [ :uri "source/cc-licenses.ttl" ; :derivedFrom ] ; 19 | :license . 20 | 21 | a :Dataset ; 22 | :created "2024-12-04T12:38:31Z"^^xsd:dateTime ; 23 | :uriSpace "https://begrepp.uka.se/SSIF/" ; 24 | :sourceData [ :uri "source/ssif-2025-skos.ttl" ; 25 | :derivedFrom ] ; 26 | :datasetClass [ :broadMatch :Concept ; :closeMatch :Classification ] . 27 | -------------------------------------------------------------------------------- /source/datasets/i18n.ttl: -------------------------------------------------------------------------------- 1 | prefix xsd: 2 | prefix : 3 | base 4 | 5 | # TODO: include these in idkbse.ttl when ready for QA! 6 | 7 | a :Dataset ; 8 | :uriSpace "https://id.kb.se/i18n/script/" ; 9 | :created "2022-10-14T16:26:16Z"^^xsd:dateTime ; 10 | :sourceData [ :uri "source/i18n/scripts.ttl" ] ; 11 | :derivedFrom . 12 | 13 | a :Dataset ; 14 | :uriSpace "https://id.kb.se/i18n/rule/" ; 15 | :sourceData [ :uri "source/i18n/rules.ttl" ] ; 16 | :created "2022-10-14T16:26:16Z"^^xsd:dateTime . 17 | 18 | a :Dataset ; 19 | :uriSpace "https://id.kb.se/i18n/lang/" ; 20 | :sourceData [ :uri "source/i18n/tlangs.ttl" ] ; 21 | :created "2022-10-24T13:32:22Z"^^xsd:dateTime . 22 | -------------------------------------------------------------------------------- /source/datasets/sab.ttl: -------------------------------------------------------------------------------- 1 | prefix xsd: 2 | prefix : 3 | base 4 | 5 | a :Dataset ; 6 | :sourceData [ :uri "source/sab.ttl" ] ; 7 | :uriSpace "/term/sab/" ; 8 | :created "2014-01-23T10:34:17.981Z"^^xsd:dateTime . 9 | -------------------------------------------------------------------------------- /source/datasets/signe.ttl: -------------------------------------------------------------------------------- 1 | prefix xsd: 2 | prefix : 3 | base 4 | 5 | a :Dataset ; 6 | :created "2022-02-19T09:06:00Z"^^xsd:dateTime ; 7 | :source ; 8 | :license . 9 | 10 | a :Dataset ; 11 | :uriSpace "https://libris.kb.se/dataset/signe/bib/" ; 12 | :created "2022-04-07T13:40:10Z"^^xsd:dateTime ; 13 | :sourceData [ :uri "../signe2librisxl/build/bibs.jsonld.lines" ] ; 14 | :isPartOf . 15 | 16 | a :Dataset ; 17 | :uriSpace "https://libris.kb.se/dataset/signe/edition/" ; 18 | :created "2022-04-19T10:00:00Z"^^xsd:dateTime ; 19 | :sourceData [ :uri "../signe2librisxl/build/editions.jsonld.lines" ] ; 20 | :dependsOn ; 21 | :isPartOf . 22 | -------------------------------------------------------------------------------- /source/datasets/syscore.ttl: -------------------------------------------------------------------------------- 1 | prefix : 2 | prefix xsd: 3 | base 4 | 5 | a :Dataset ; 6 | :sameAs ; 7 | :inDataCatalog ; 8 | :publisher ; 9 | :label "The Dataset of all Libris Datasets"@en . 10 | 11 | a :Dataset ; 12 | :isPartOf ; 13 | :created "2022-11-19T13:09:35.010Z"^^xsd:dateTime ; 14 | :sourceData [ :uri "build/syscore.jsonld.lines" ; 15 | :sourceData [ :tool ] ] . 16 | -------------------------------------------------------------------------------- /source/doc/about.sv.mkd: -------------------------------------------------------------------------------- 1 | # Om id.kb.se 2 | 3 | ## Inledning 4 | 5 | **id.kb.se** är en tjänst som tillgängliggör de grundstenar Kungliga biblioteket använder för att publicera strukturerade, [länkade data](https://sv.wikipedia.org/wiki/L%C3%A4nkad_data). Här finns gemensamma definitioner och begrepp som hjälper till att samordna beskrivningar av vårt material. Dessa utgår från och länkar till internationella, välkända motsvarigheter i möjligaste mån. 6 | 7 | Länkade data är en metod för att publicera strukturerade data på ett sätt som gör det möjligt för maskiner att skapa kopplingar mellan olika informationsobjekt och datakällor. På KB finns mycket data som kan länkas samman med annan data på Internet och på så sätt möjliggöra att datat berikas åt båda håll. 8 | 9 | ## Identifikatorer 10 | 11 | Varje begrepp, som t.ex. ett ämnesord, identifieras med hjälp av en HTTP-URI. URI:n består av en adress och en textsträng som tillsammans utgör begreppets unika identifikator. Exempel: `https://libris.kb.se/rp354vn9510f7x9`. 12 | 13 | Du som använder begreppen i egna system bör lagra de kompletta strängarna (`https://...`) i din databas. 14 | 15 | Vissa begrepp får även en mer "läsbar" identifikator baserat på den föredragna benämningen för begreppet. Exemplet ovan, som identifierar ämnesordet "Kommunikation", har fått den alternativa identifikatorn: `https://id.kb.se/term/sao/Kommunikation` Men du bör använda formen `https://libris.kb.se/rp354vn9510f7x9` när du lagrar den lokalt i ditt system. 16 | 17 | ## Metadataformat 18 | 19 | Under flikarna Basvokabulär och MARC-mappningar visas formatet utifrån olika perspektiv.  20 | Bakomliggande metadata presenteras för närvarande i 3 olika syntaxer: 21 | 22 | * [JSON-LD](http://json-ld.org/) (JavaScript Object Notation for Linked Data) 23 | * [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/), ett format för att uttrycka RDF (Resource Description Framework) i XML. 24 | * [Turtle](http://www.w3.org/TR/turtle/) (Terse RDF Triple Language), ett format för att uttrycka RDF i en syntax som liknar SPARQL. 25 | 26 | Vår metadata uttrycks för närvarande med ett format baserat på [BIBFRAME 2.0](http://www.loc.gov/bibframe/docs/index.html), kompletterat med etablerade standarder, bl.a. 27 | [Dublin Core](http://purl.org/dc/terms/), 28 | [SKOS](http://www.w3.org/2004/02/skos/core#) och 29 | [MADS/RDF](http://www.loc.gov/mads/rdf/v1#). 30 | 31 | Dessa urskiljs i metadata med olika prefix som`bf`, `dc:`, `skos:`, och `madsrdf:`, med flera. De flesta vokabulären är beskrivna på ett sådant sätt som möjliggör att man ska kunna få ut data i flera olika etablerade standarder. 32 | 33 | En del information som är av mer administrativ karaktär och där vi ännu inte funnit motsvarande standardvokabulär har gett "lokala" prefix såsom `marc:` och `kbv:`. Termer som har prefixet `marc:` kommer inte att underhållas för persistens över tid medans kbv-termerna kan räknas till en mer stabil karaktär, även om dessa också kan innehålla kategorier för experimentella konstruktioner. För mer detaljer kring det och den datamodell vi använder, se artikeln [Data Model (på engelska)](https://github.com/libris/definitions/wiki/Data-Model). 34 | 35 | 36 | -------------------------------------------------------------------------------- /source/doc/issues.en.mkd: -------------------------------------------------------------------------------- 1 | # Design Issues 2 | 3 | We document our design as issues we want to or have resolved. 4 | 5 | Detailed design issues have their own documents. 6 | 7 | * [Record/Thing](/doc/issues/record-thing) (solved) 8 | * [Concept/Thing](/doc/issues/concept-thing) (solved) 9 | * [Content/Carrier](/doc/issues/content-carrier) (open) 10 | * [Literal Structures](/doc/issues/literal-structures) (open) 11 | 12 | Following are sections of open discussions that we haven't yet formalized into specific issues. 13 | 14 | ## Prefer domainIncludes/rangeIncludes over Abstract Base Classes 15 | 16 | Schema.org defines the properties http://schema.org/domainIncludes and http://schema.org/rangeIncludes, which may be preferable to our current abstact Endeavor and Creation classes which groups Work, Instance and Item classes based on the "text only" definitions of these groups in BibFrame 2. 17 | 18 | ## Categorize Classes For Various Uses 19 | 20 | Since we need to stay compatible with MARC21, but not fully restricted by it, we need to explain when data is fully compatible and when it extends beyond what (our mechanics for dealing with) MARC21 is capable of. 21 | 22 | We have yet to formally categorize classes belonging to e.g. a "managed" domain (those that we have aligned with BF2 and RDA value terms, which are also mappable from (and back to) MARC"). In doing so, in order to promote other valuable classes (i.e. those useful for covering specific forms of content and material which are missing in both BF2 and RDA), we need to mark unstable notions (`:status :TermTesting`). 23 | 24 | ## BibFrame Assessment 25 | 26 | ### Documentation: 27 | - English documentation literals should have language tags 28 | - some labels don't match the property (e.g. "Provider" for provisionActivity). 29 | - texts seem generated, which is fine and good, but "Unspecified" texts seem to be errors... 30 | - Pedantic: 31 | - link to license (CC0 according to ): 32 | dc:license 33 | - link rdfs:isDefinedBy <> 34 | 35 | ### Domains and Ranges 36 | - Some domains and ranges should be given (e.g. :media -> :Media; music`*` of :Music...), at least with *hints* (schema:domainIncludes, schema:rangeIncludes) 37 | - use schema:domainIncludes instead of "Used with" (or define a common base class of Work nd Instance (and Item?) 38 | - Either a common base class, or make a hierarchy: Item < Instance < Work? 39 | 40 | ### Serials 41 | - No more Serial. Could an instance/work also be a Collection? Should it be a subclass of Instance (BF1)? (Serial can be another "slice of general" than Item :moreGeneral Instance; i.e. Item :moreGeneral :Serial, where shared characteristics are based on "common over time", not "common over forms") 42 | UPDATE: optional issuance with values being subclasses of Issuance, like Serial or Integrating? 43 | 44 | ### provisionActivityStatement 45 | - It seems wise(? not necessarily for a single instance?) to be able to attach the transcribed provisionActivityStatement to the associated (derived) provisionActivity. Either put the statement as is in the entity (using rdfs:label?), or use a generic property (rdfs:label) to represent the string? Best to be able to say that it was transcribed (e.g. if implied when using the statement property, as opposed to a too generic one) 46 | 47 | ### Types 48 | How about replacing the various type-properties (ensembleType, instrumentalType, voiceType, noteType, variantType) with either a pattern for using rdf:type with bnodes having rdfs:label, or a generic property for giving a type hint as a literal (possibly with owl:propertyChainAxiom (rdf:type rdfs:label))? 49 | 50 | ### Notes and Statements 51 | 52 | Imperfect data... "raw and partial" stuff, put into notes... 53 | 54 | See also [Mappings] for how to simplify these (and expand simple forms, respectively). 55 | 56 | 57 | * Notes: typeNote vs. type/notes (and similar for all such divisions...; for presentation/edit: difference between linked entity and inlined description) 58 | * Are `*Statement` notes? No, they are "text values on source" (more like rdf:value). Also, reorganize into proper entity (:publicationStatement rdfs:domain :Publication) 59 | 60 | ### Coordinate Sub-Type Forms using Fixed (Enumeration) Values 61 | 62 | Make: 63 | 64 | <> a :EBook . 65 | 66 | be isomorphic to: 67 | 68 | <> a :Book ; 69 | :format :EBook . 70 | 71 | and coordinated with: 72 | 73 | <> a sdo:CreativeWork, sdo:Book ; 74 | sdo:bookFormat sdo:EBook . 75 | 76 | and somehow with (the object of hasFormat in): 77 | 78 | <> a bibo:Book ; 79 | dc:hasFormat [ sdo:EBook ] . 80 | 81 | ### Value Corrections 82 | 83 | Correlate (and "error correct") StructureValue and Datatyped Literals. Example: 84 | 85 | <> :identifier [ a :ISBN ; 86 | rdf1:value "123-456-789-0" ] . 87 | 88 | <> :identifier "123-456-789-0"^^:ISBN . 89 | 90 | <> :identifier "123-456-789-0" . 91 | 92 | ### Link Corrections 93 | 94 | - strings as resource hints 95 | - define tokens and coerce to URI 96 | - error-correct 97 | - search on label/value/notation ... 98 | - coerce string and xsd:anyUri to URI *but never URI to string unless term is datatyped* 99 | 100 | - close target forms (like "match close sub-property" (e.g. Term.name =~ Concept.prefLabel); using "infer restricted domain form" or closeMatch...) 101 | 102 | ### Other 103 | 104 | - magic properties (prefLabel from component properties (of the concept or its focus)) 105 | -------------------------------------------------------------------------------- /source/doc/issues/concept-thing.en.mkd: -------------------------------------------------------------------------------- 1 | # Concept/Thing Design Issue 2 | 3 | We have opted out of the previous LIBRIS design of using Concepts as nominal entities with a "real world" thing focus. The reason for this is long-winded and discussions border on the absurd, but the gist of it is: focus on the thing. While the description may be skewed, biased or even conflated with local business practise, hiding that behind a nominal entity for the "term" makes it even harder to discuss these differences and why they have come to be. Furthermore, attempts to isolate the Concept from the thing leads to a "parallell" representation of facts about the thing, indirected via abstract relations such as "broaderPartitive", where a more concrete modeling more effectively deals with the actual relation that's being modeled. 4 | 5 | With that said, we do allow for "nominal" skos:Concept structures *in place* of real-world entities, since sometimes skeleton concepts is all that a particular working group can produce (e.g. flat genre thesauri in place of a full ontology about expressions, their meanings and forms). 6 | 7 | See also: 8 | - [Getty-Vocab-Concept-Thing][]. 9 | - Use/Mention distinction 10 | - Sign/Signified 11 | - An amusing, related example of going way too far on the nominal side of thing,s see [HaddocksEyes][]. 12 | 13 | ## Naive vs Nominal 14 | 15 | Naive Realism vs nominalism. 16 | 17 | Is Nominal also Naive? 18 | 19 | Names versus Thing 20 | 21 | Name? "Nomen" in e.g. Library Reference Model (in turn from FRSAD, c.f. FRAD Name/Controlled Access Point)? rdfs:label? schema:name? skos:prefLabel?? rdfs:label? schema:name? skos:prefLabel? 22 | 23 | There are no things without names. So we're inescapably using language to paint reality. 24 | 25 | Design Goal: keep things simple and understandable. Ground controlled structures of description in "naive" concepts. 26 | 27 | ## Design Decision: Topic Items versus Computed "Chips" 28 | 29 | A *very* normalized and unified shape is the notion of a nominal "topic", which is akin to skos:Concept, and also to atom:Entry. It is a simplified digest of an entity, an outline, sketch or silhouette which only hints, nor describes in detail. 30 | 31 | Forcing descriptions into such a package results in data loss and sometime skewed data. But it can be effective for simple listings and navigation, and to some extent (textual) search. 32 | 33 | Is this "summary" a distinct entity, separate from the record and the focus? Can it be defined by the things that are part of a templated skos:prefLabel, in conjunction with its distinctive type, and its distinctive (main source and/or dataset?) "membership" related using skos:inScheme (what of madsrdf:inCollection - reasonably part of the "type" here)? Or is this too awkward to model and maintain? Is it actually just a conceptual specification, to which concrete abbreviated forms should roughly adhere? (But then again, is the abbreviated form a distinct notion...?) 34 | 35 | See: 36 | - https://www.google.se/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=skos+concept+%22library+business+object%22 37 | - https://lists.w3.org/Archives/Public/public-esw-thes/2012Nov/0011.html 38 | - https://lists.w3.org/Archives/Public/public-esw-thes/2009Nov/0063.html 39 | 40 | (See also bibframe 1 lightweight abstractions and authorities...?) 41 | 42 | "Solution"(?): allow crossing the abstraction/concretization barrier, as in linking to the abstraction, or moving properties in the abstraction into the concretization... (making search simpler, apart from how to fuse their types (suggestion: keep concretization only)) 43 | 44 | -------------------------------------------------------------------------------- /source/doc/issues/literal-structures.en.mkd: -------------------------------------------------------------------------------- 1 | # Literal Structures 2 | 3 | ## The Use of Language 4 | 5 | Compare uses of Language Tags with presence of bf:descriptionLanguage, and also possible interpretations of language of a Work or Concept for determining language of plain strings in the title or prefLabel. 6 | 7 | ## Extended Literals (SKOS-XL) as Structured Values 8 | 9 | To represent transliterated forms. To be decided. 10 | 11 | ### Literals and Structured Values 12 | 13 | .. Error-correct untyped strings (vs. keeping differently cast terms in JSON-LD) 14 | 15 | .. Soft-match partial datatype values (dates... 16 | 17 | .. turn into rdf:value + :year, :month, :day? (as "virtual" properties of a Datatype) 18 | .. Shrink to Datatype Literals 19 | 20 | .. Interpret Labels and Annotations (c.f. schema:PropertyValue) 21 | -------------------------------------------------------------------------------- /source/doc/issues/record-thing.en.mkd: -------------------------------------------------------------------------------- 1 | # Record/Thing 2 | 3 | * Some items are just documents; we still put their administrative metadata in a "record", but note that they are embedded? 4 | 5 | * for all other things, sparse records administrate the things (even vocab and skos terms) 6 | 7 | * Some items are just things (persons, websites, ...), but *really*, their descriptions are (partial) records (e.g. dbpedia quotes), even if empty (though at least @id + dc:modified...) 8 | 9 | * Mostly(?), if a thing is given, it's the most interesting entity (possible conflict: AgentTerm and Agent...; from the library management POV, not for discovery...) 10 | 11 | SOURCE: 12 | 13 | <1> [ 14 | {id: 1, a Record, mainEntity 1#term}, 15 | {id: 1#it, a Person} 16 | {id: 1#term, a Term; focus 1#it} 17 | ] 18 | 19 | INDEX: 20 | 21 | Term 22 | focus 23 | Person 24 | meta 25 | Record 26 | 27 | SOURCE: 28 | 29 | <2> [ 30 | {id: 2, a Record, mainEntity 2#it}, 31 | {id: 2#it, a Person} 32 | ] 33 | 34 | INDEX: 35 | 36 | Person 37 | meta 38 | Record 39 | 40 | SOURCE: 41 | 42 | <2> [ 43 | {id: 3, a Document} 44 | ] 45 | 46 | INDEX: 47 | 48 | Document 49 | meta 50 | Record (known-admin-parts-of) 51 | 52 | We *no longer* use: 53 | 54 | GRAPH { 55 | 56 | 57 | :mainEntity . 58 | 59 | 60 | :meta ; # description 61 | :focus . # concretization 62 | 63 | 64 | :meta ; 65 | :hasAuthority . # abstraction 66 | 67 | } 68 | 69 | (See also [Concept/Thing Design Issue](/doc/issues/concept-thing).) 70 | -------------------------------------------------------------------------------- /source/doc/model.en.mkd: -------------------------------------------------------------------------------- 1 | Data Model 2 | =========== 3 | 4 | The description of the data model has been been relocated to [https://github.com/libris/definitions/wiki/Data-Model](https://github.com/libris/definitions/wiki/Data-Model). -------------------------------------------------------------------------------- /source/doc/philosophy.en.mkd: -------------------------------------------------------------------------------- 1 | Philosophy 2 | ========================================== 3 | 4 | ## Purpose 5 | 6 | Our goal is for bibliographic data to be usable and interlinkable across sources, applications and institutions. It doesn't have to be complete, but it should be possible to use with other descriptions, in order to form more complete pictures than the parts represent. RDF is an excellent common starting point for this, but the choice, development and practical interoperability of vocabularies are key to enabling these possibilities. 7 | 8 | Therefore, we want to use and build upon existing vocabularies as much as possible. If our data is understandable, and we can understand the data of others, if just to some degree of common ground, we have the ability to improve and integrate further. 9 | 10 | 11 | ## Requirements 12 | 13 | An effective vocabulary is used to capture distinct and commonly understood information about distinct and commonly understood (types of) things. 14 | 15 | To measure this, existing practise and needs have to be understood, and that understanding used to measure the effect of proposed pieces of description. It helps to focus on what is *really* sought after (such as the title of one of several variations of a conceptually distinct piece of content), rather than particular forms of description (e.g. "using MARC21 notation, type 246 and then supply pieces under a for the main title, b for the remainder, and so on"). To correlate with existing vocaburies (and abstract reference models) is also important (at least as long as these themselves represent a viable effort taking the above considerations into account). 16 | 17 | To not get caught up in this indefinitely, it helps to understand that there is no "correct" form of description. There are more or less effective ways to convey the necessary information for a given set of purposes. Compare this to the drawing of maps. No one would expect a coast line to be drawn to an accuracy under a certain resolution threshold. At the same time, we would expect to be able to trace the route from A to B and clearly see that they are separated by a bay or a river. 18 | 19 | Rich information can be useful to a point, but can obscure the simple core. A simplistic or naive description can miss the point entirely, if distinct differences are required to differentiate between similar items. 20 | 21 | A method for eliminating unnecessary (or hard-to-grasp) differences is key. A point of difference need to be clearly described and documented for it to be upheld in practise, when the description is created or used. 22 | 23 | ## Stuff To Include 24 | 25 | It is folly to fully categorize these notions. Some are fleeting, and many overlap. No matter the psychological or philosophical stance, that work will never end. *Nevertheless, it helps to understand that. To avoid getting stuck in either ignorant choices or overthought designs.* 26 | 27 | Artefacts: artefacts of various forms and material 28 | Cultural: genres, natures, content 29 | Temporal: events, provisioning, revisions 30 | Social: names, agents, languages, countries, nationalities 31 | 32 | Build a class hierarchy to explicitly reflect the chosen structure (using multiple inheritance if necessary). Mark certain classes as abstract and/or "top concepts", to use as starting points for listing/navigation, and for avoiding selection in creation of new descriptions. 33 | 34 | When determining their merits and applicability, we need to consider a number of questions. What are they used for? By whom? Where do they originate? How are they maintained? How do they correlate? 35 | 36 | ## Choice Guided By Usage 37 | 38 | The primary way to measure the value of our data design is to actively use it. We need to include it in a concrete data and service infrastructure, which we develop iteratively by implementing real applications using it (such as import processes, export routines, navigation and editing interfaces). These determine the context(s) which we, and thus our data model, have to understand, convey and support. 39 | 40 | Any given slicing is an approximation and contextually dependent. Is the "core" type to partition on limited to: a) WEMI-level? B) content/media/carrier? C) Only Text, or Novel/Article/...? Put non-"core" in other prop? Like additionalType, genre? In principle: use a type if it brings possible new properties with it, or functions as a common significant distinguisher. 41 | 42 | May need a backing "exploded" model for weird MARC where folding isn't possible (multiple carriers, oddly repeated 006...). Don't put all format notes from fields into "hasFormat", but rather distinguish "extentNote" etc, and then fold all if there's not more than one of each... 43 | 44 | ## Levels of Ambition (Deciding the Domain) 45 | 46 | How much of old/existing nomenclature/practise is preserved and discarded, respectively? (E.g. 100, 700, ...) 47 | 48 | How is that measured? By ability to convert MARC? Ability to fit other data into the same output (DC, MODS, BIBO, ONIX, Schema.org)? Ability to re-convert ("revert") to MARC? Ability to use in new kinds of interfaces for editing, searching/browsing/filtering/clustering? 49 | 50 | Test in implementation, applied usage, data flow scenarios, and reiterate as needed. 51 | 52 | -------------------------------------------------------------------------------- /source/doc/services.en.mkd: -------------------------------------------------------------------------------- 1 | Services 2 | =========== 3 | 4 | We focus on a stable core to build services upon, grounded in the data model. 5 | 6 | There will be details in flux, primarily related to the domain vocabulary, that need to be configurable/generalizable to let services catch up with ongoing changes to the data (as we continue to understand and evaluate its shapes in relation to explicit use cases). 7 | 8 | ## REST APIs 9 | 10 | We use JSON-LD as our primary format for services. 11 | 12 | .. Read API 13 | - conneg 14 | - views 15 | .. Search API 16 | .. Write API 17 | 18 | ### Searching 19 | 20 | 21 | 22 | 23 | 24 | 25 | ## Access 26 | 27 | ### Identifiers 28 | 29 | Same As, redirects... 30 | 31 | ### Records and Graphs 32 | 33 | Named Graphs: default graph plus quotes as partial named graphs 34 | 35 | .. different Views for mappings/alignments? (E.g. schema.org from BF2.) 36 | 37 | #### Organizing (Quoting and Refactoring) Descriptions 38 | 39 | For inlined description: differ between "unknown" and quoted (the latter either has @id or (perhaps better, until properly refactored?) sameAs (also, if refactored (as in extracted to own record) still better to keep sameAs (it will have an @id, within our system)). 40 | 41 | #### System Partitioning of descriptions, delegation and indirection 42 | 43 | Of interest: 44 | 45 | * owning a resource vs. owning a description 46 | * minting in abcence of URI stewardship 47 | * maintaining mappings over time 48 | * rewriting core data vs. expanding when publishing 49 | 50 | ### Views: Cards and Chips 51 | 52 | - required: chips and cards for search/navigation/context 53 | 54 | Select parts of full descriptions. Used for interface design, through our data APIs and search mechanics. 55 | 56 | Defined using display descriptions for selected classes, based on a small subset of the FRESNEL terminology... 57 | 58 | ### Embellished Data 59 | 60 | - linked resources as cards and chips 61 | - additional: incoming links for discovery (we have holdings, cover images, local copies, additional metadata, external reviews...) 62 | 63 | - vocab expansions/shedding, alignments...) 64 | 65 | ### Using Mappings 66 | 67 | .. explain framing, embellish, folding paths as simple properties, rdfa:copy, ... 68 | 69 | Variants of similar notions, as in terms and shapes of expression. Preferably one shape, in practise lots of variation due to limited knowledge, understanding, interest or time. 70 | 71 | Usage thereof ranges from the simple needs to detailed access. Quick detection (pick for display then scanning surface for context). Detailed filtering, picking "angle" ("classified using notion X", "filtered on given dates on material", "relation to group of creators"...). 72 | 73 | * Specialized Mappings: 74 | - for (es-)indexing 75 | - "casual" usage/presentation 76 | - for editing: e.g. "new Book -> contentText, carrier Volume, bookFormat ..." 77 | 78 | * Generalized Mappings (RDA<->BF, new methods for import/export) 79 | 80 | ### Mapping Identities 81 | 82 | .. sameAs, closeMatch, broadMatch (and where it comes in play for import/export (including MARC)). 83 | 84 | Smushing – merging two linked entities into one. 85 | 86 | ### Mapping Shapes 87 | 88 | StructuredValue & Role 89 | 90 | - expansions and foldings (all supertypes, simple properties from chains) 91 | 92 | Define paths to/between shapes. Select target shape and reshape input. Access expanded shape using simplified "lenses" selected at runtime ("get me some label for X"). 93 | 94 | Form: 95 | * Things: generalize/specialize, abstract/concrete 96 | * Records (descriptions): properly meta, "outside" 97 | 98 | Details: 99 | * Structured Values 100 | * Reified Values 101 | 102 | Access: 103 | * compute (label) properties 104 | * get summary chips (get intrinsic properties and relations (owl:key? fresnel?); use folded forms) 105 | 106 | Pre-index: 107 | * embellish: combine folded and expanded ("all the variants") 108 | 109 | ### Mapping Vocabularies 110 | 111 | Simplest mapping is a regular JSON-LD context. 112 | 113 | Use ld-context (generation) to unify superficial vocab term name differences. 114 | 115 | -------------------------------------------------------------------------------- /source/doc/standards.en.mkd: -------------------------------------------------------------------------------- 1 | Data Standards 2 | ============== 3 | 4 | ### MARC 5 | 6 | Data in MARC is rather denormalized, contains a redundancy of information and local uses of code lists with little to no explicit dereferencability without access to out-of-band information which may sometimes be obscured by changes over time. 7 | 8 | At the same time, a lot of the concepts of bibliographic practise is oriented around practises in using MARC. Special needs, required by experts in certain domains, are often represented only through the use of specialized forms of description that have no formal definition outside of MARC. 9 | 10 | ### RDF 11 | 12 | Being a standard model for the exchange of structured, linked data on the web, RDF may be seen as a given, especially in the library context. It must be emphasised though, that RDF is merely a tool. Without proper application, its potential will remain untapped. We have chosen it based on a number of merits. 13 | 14 | It constitutes a logical, representation independent data model. That lets us design concrete data structures without tying them to one serialization format. 15 | 16 | The triple representation, grounded in formal logic, makes for a structurally flat model upon which navigation and reasoning may be applied as needed. This makes for some key features: 17 | 18 | * A clear notion of entities with identities and explicit, open types. 19 | * Naturally mixable descriptions making (superficial) data integration technically simple. 20 | * Simple publication of and access to structured data and vocabularies using HTTP-URIs, independent of application and platform limitations. 21 | 22 | RDF has the *capacity* for structured extensions, correlation and semantic interoperability. 23 | 24 | It is structurally interoperable in itself, meaning that descriptions can be merged at will from disparate sources, with disparate semantics. This, however, places sometimes tremendous burden upon the consumer. This is not acceptable, and needs to be used to place requirements upon vocabulary designers and data producers (or providers). 25 | 26 | ### RDA 27 | 28 | 29 | ### BibFrame 30 | 31 | We are mapping (most of) the data we have in MARC21 into RDF descriptions. This is only the beginning of a more efficient form for bibliographic descriptions. 32 | 33 | We want to avoid inventing more vocabulary, and ensure our mappings works both ways (we have a fairly isomorphic two-way MARC/BF2 conversion in place). For this, BF2 is the best fit so far. That is to say, best from a conversion perspective, and *probably* for supporting the mindset set by cataloguing rules (not the least of which is RDA). From a usability perspective and a theoretical modeling perspective, we cannot say the resulting forms are optimal. But we do believe that there is possibility of consolidation if we do this, and that the worst shackles of MARC21 can be broken. 34 | 35 | From this we can start to fill in blanks and improve descriptions of new forms of material. Thus we have a practical path forward with BF right now. This is in part due to the closeness to some prevalent MARC21 idioms. No matter how convoluted they might be (title entities, provision entities and such), at least getting rid of string codes in favour of links to defined terms, and enabling the linking of entities over shuffling intricate bundles of strings around is a major improvement for systems design, discovery and comprehensibility. 36 | 37 | We are currently adapting our target vocabulary to BibFrame 2 where possible. Ideally, we would have no vocabulary of our own. But given the state of stability and consensus in the library world regarding these matters, we have no choice but to map and adapt as we go. The change from BibFrame 1 to 2 seemed to us a good step in our own direction. 38 | 39 | We would be happy to participate in its stabilization as much as possible (given our limited staff and present workload). 40 | 41 | ### Schema.org 42 | 43 | A quick analysis: semi-ordered, rough consensus, "dirty" but usable. Not a formal model, but it has some good parts to relate to. 44 | -------------------------------------------------------------------------------- /source/doc/summary.sv.mkd: -------------------------------------------------------------------------------- 1 | **id.kb.se** är en tjänst som tillgängliggör de grundstenar Kungliga biblioteket använder för att publicera strukturerade, [länkade data](https://sv.wikipedia.org/wiki/L%C3%A4nkad_data). Här finns gemensamma definitioner och begrepp som hjälper till att samordna beskrivningar av vårt material. 2 | -------------------------------------------------------------------------------- /source/encodingFormat-terms.ttl: -------------------------------------------------------------------------------- 1 | @prefix : . 2 | @prefix rdfs: . 3 | @prefix owl: . 4 | @prefix sdo: . 5 | @prefix skos: . 6 | @prefix iana: . 7 | @prefix lcmenc: . 8 | @base . 9 | 10 | # TODO: Add TAR 11 | 12 | a :EncodingFormat ; 13 | rdfs:label "JSON" ; 14 | skos:notation "application/json" ; 15 | skos:exactMatch iana:application\/json . 16 | 17 | a :EncodingFormat ; 18 | rdfs:label "JSON-LD" ; 19 | skos:notation "application/ld+json" ; 20 | skos:exactMatch iana:application\/ld\+json . 21 | 22 | a :EncodingFormat ; 23 | rdfs:label "MARC" ; 24 | skos:notation "application/marc" ; 25 | skos:exactMatch iana:application\/marc . 26 | 27 | a :EncodingFormat ; 28 | rdfs:label "MARCXML" ; 29 | skos:notation "application/marcxml+xml" ; 30 | skos:exactMatch iana:application\/marcxml\+xml . 31 | 32 | a :EncodingFormat ; 33 | rdfs:label "MSWord" ; 34 | skos:notation "application/msword" ; 35 | skos:exactMatch iana:application\/msword . 36 | 37 | a :EncodingFormat ; 38 | rdfs:label "PDF" ; 39 | skos:notation "application/pdf" ; 40 | skos:exactMatch iana:application\/pdf . 41 | 42 | a :EncodingFormat ; 43 | rdfs:label "RDF/XML" ; 44 | skos:notation "application/rdf+xml" ; 45 | skos:exactMatch iana:application\/rdf\+xml . 46 | 47 | a :EncodingFormat ; 48 | rdfs:label "ZIP" ; 49 | skos:notation "application/zip" ; 50 | skos:exactMatch iana:application\/zip . 51 | 52 | a :Classification ; 69 | skos:prefLabel "Sakkunninggranskad publiceringskanal"@sv ; 70 | rdfs:comment "Kanalen är specialiserad på publicering av vetenskapliga forskningsresultat och har en oberoende sakkunniggranskning av vetenskaplig eller konstnärlig kvalitet."@sv ; 71 | rdfs:comment "Publication channel specialized in publishing scientific research results and has an independent peer review of scientific or artistic quality."@en . 72 | 73 | a :Classification ; 74 | skos:prefLabel "Icke-sakkunninggranskad publiceringskanal"@sv ; 75 | rdfs:comment "Kanalen uppfyller inte kriterierna för sakkunniggranskad publiceringskanal."@sv ; 76 | rdfs:comment "Publication channel that does not meet the criteria for a peer-reviewed publication channel."@en . 77 | 78 | a :Status ; 79 | skos:prefLabel "Published in DOAJ-indexed journal"@en, "Publicerad i DOAJ-indexerad tidskrift"@sv; 80 | rdfs:comment "Resurs publicerad i DOAJ-indexerad tidskrift (Directory of Open Access Journals)."@sv ; 81 | rdfs:comment "Resource published in a DOAJ-indexed journal (Directory of Open Access Journals)."@en . 82 | -------------------------------------------------------------------------------- /source/swepub/types.ttl: -------------------------------------------------------------------------------- 1 | prefix : 2 | prefix skos: 3 | prefix swepub: 4 | prefix sppubl: 5 | prefix spout: 6 | 7 | swepub:ArtisticWork a :GenreForm ; 8 | skos:exactMatch sppubl:kfu, spout:artistic-work . 9 | 10 | swepub:Book a :GenreForm ; 11 | skos:exactMatch sppubl:bok, spout:publication\/book . 12 | 13 | swepub:BookChapter a :GenreForm ; 14 | skos:exactMatch sppubl:kap, spout:publication\/book-chapter . 15 | 16 | swepub:ConferencePaper a :GenreForm ; 17 | skos:closeMatch sppubl:kon\/ref, spout:conference\/paper . 18 | 19 | swepub:DoctoralThesis a :GenreForm ; 20 | skos:exactMatch sppubl:dok, spout:publication\/doctoral-thesis . 21 | 22 | swepub:EditorialCollection a :GenreForm ; 23 | skos:exactMatch sppubl:sam, spout:publication\/edited-book . 24 | 25 | swepub:EditorialProceedings a :GenreForm ; 26 | skos:exactMatch sppubl:pro, spout:conference\/proceeding . 27 | 28 | swepub:JournalArticle a :GenreForm ; 29 | skos:closeMatch sppubl:art\/ref, spout:publication\/journal-article . 30 | 31 | swepub:LicentiateThesis a :GenreForm ; 32 | skos:exactMatch sppubl:lic, spout:publication\/licentiate-thesis . 33 | 34 | swepub:OtherPublication a :GenreForm ; 35 | skos:exactMatch sppubl:ovr, spout:publication . 36 | 37 | swepub:Patent a :GenreForm ; 38 | skos:exactMatch sppubl:pat, spout:intellectual-property\/patent . 39 | 40 | swepub:Report a :GenreForm ; 41 | skos:exactMatch sppubl:rap, spout:publication\/report . 42 | 43 | swepub:ResearchReview a :GenreForm ; 44 | skos:exactMatch sppubl:for, spout:publication\/review-article . 45 | 46 | swepub:Review a :GenreForm ; 47 | skos:exactMatch sppubl:rec, spout:publication\/book-review . 48 | 49 | -------------------------------------------------------------------------------- /source/swepub/update.rq: -------------------------------------------------------------------------------- 1 | prefix swe: 2 | prefix skos: 3 | 4 | insert { 5 | ?term skos:inScheme 6 | } where { 7 | ?term ?p ?o 8 | filter ((?term != swe:) && strstarts(str(?term), str(swe:))) 9 | }; -------------------------------------------------------------------------------- /source/swepub/vocab.ttl: -------------------------------------------------------------------------------- 1 | prefix rdfs: 2 | prefix swepub: 3 | prefix : 4 | 5 | swepub:PublicationType a :EnumerationClass ; 6 | rdfs:subClassOf :GenreForm ; 7 | :category :pending . 8 | 9 | swepub:OutputType a :EnumerationClass ; 10 | rdfs:subClassOf :GenreForm ; 11 | :category :pending . 12 | 13 | swepub:ContentType a :EnumerationClass ; 14 | rdfs:subClassOf :GenreForm ; 15 | :category :pending . 16 | 17 | swepub:HostType a :EnumerationClass ; 18 | rdfs:subClassOf :GenreForm ; 19 | :category :pending . 20 | 21 | swepub:PublicationStatus a :EnumerationClass ; 22 | rdfs:label "Publication status"@en, "Publiceringsstatus"@sv ; 23 | rdfs:comment "Anmärkningar rörande verkets status"@sv; 24 | rdfs:subClassOf :Note ; #TODO Status?; 25 | :category :pending . 26 | -------------------------------------------------------------------------------- /source/tactilenotation.ttl: -------------------------------------------------------------------------------- 1 | @prefix rdfs: . 2 | @prefix owl: . 3 | @prefix sdo: . 4 | @prefix skos: . 5 | @prefix : . 6 | @base . 7 | 8 | @prefix rdaftn: . 9 | @prefix marctac: . 10 | 11 | # TACTILE NOTATION linked to id.loc.gov and rdaregistry.com 12 | 13 | a :TactileNotation ; 14 | skos:prefLabel "Punktskrift"@sv ; 15 | skos:notation "1001" ; 16 | :code "brail" ; 17 | skos:exactMatch rdaftn:1001, marctac:brail . 18 | 19 | a :TactileNotation ; 20 | skos:prefLabel "Punktskrift för databehandling"@sv ; 21 | skos:notation "1002" ; 22 | :code "comp" ; 23 | skos:exactMatch rdaftn:1002, marctac:comp . 24 | 25 | a :TactileNotation ; 26 | skos:prefLabel "Matematisk och naturvetenskaplig punktskrift"@sv ; 27 | skos:notation "1003" ; 28 | :code "math" ; 29 | skos:exactMatch rdaftn:1003, marctac:math . 30 | 31 | a :TactileNotation ; 32 | skos:prefLabel "Moonskrift"@sv; 33 | skos:notation "1004" ; 34 | :code "moon" ; 35 | skos:exactMatch rdaftn:1004, marctac:moon . 36 | 37 | a :TactileNotation ; 38 | skos:prefLabel "Punktskrift för musik"@sv ; 39 | skos:notation "1005" ; 40 | :code "music" ; 41 | skos:exactMatch rdaftn:1005, marctac:music . 42 | 43 | a :TactileNotation ; 44 | skos:prefLabel "Taktil musiknotation"@sv ; 45 | skos:notation "1006" ; 46 | :code "tactm" ; 47 | skos:exactMatch rdaftn:1006, marctac:tactm . 48 | 49 | a :TactileNotation ; 50 | skos:prefLabel "Taktil grafik"@sv ; 51 | skos:notation "1007" ; 52 | :code "graph" ; 53 | skos:exactMatch rdaftn:1006, marctac:graph . -------------------------------------------------------------------------------- /source/update-enums.rq: -------------------------------------------------------------------------------- 1 | prefix skos: 2 | prefix : 3 | 4 | insert { 5 | ?coll skos:member ?term . 6 | } where { 7 | ?coll a :CollectionClass . 8 | ?term a ?coll . 9 | } 10 | -------------------------------------------------------------------------------- /source/vocab/accessibility.ttl: -------------------------------------------------------------------------------- 1 | @prefix rdf: . 2 | @prefix rdfs: . 3 | @prefix owl: . 4 | @prefix xsd: . 5 | @prefix dc: . 6 | @prefix sdo: . 7 | @prefix vs: . 8 | @prefix : . 9 | 10 | # TODO: Decide: 11 | # - URI spaces, for example schema terms or general a11y 12 | # - skos:notation must be unique 13 | # - term status 14 | # - property vs subClasses 15 | 16 | # NOTE: This a first version of accessibility metadata to comply with European Accessibility Act 17 | # 18 | # all terms marked with status unstable because current ongoing international work (2025-05-15) and might be subject to change or restructure . 19 | # read more about the structure and usage: 20 | # 21 | # 22 | # schema vocabulary https://www.w3.org/community/reports/a11y-discov-vocab/CG-FINAL-vocabulary-20241209/ 23 | # display guide https://www.w3.org/community/reports/publishingcg/CG-FINAL-a11y-display-guidelines-20250422/ 24 | # crosswalks https://w3c.github.io/a11y-discov-vocab/crosswalk/ 25 | # LC scheme https://id.loc.gov/vocabulary/accesscontentschemes/sapdv.html 26 | 27 | 28 | 29 | ################################### 30 | # SCHEMA ACCESSIBILITY PROPERTIES # 31 | ################################### 32 | 33 | # NOTE: The metadata field accessibilityAPI and accessibilityControl does not apply to digital publications directly but rather to reading system software. 34 | # Not used by MTM for now: 35 | # 36 | # sdo:accessibilityAPI 37 | # sdo:accessibilityControl 38 | 39 | # NOTE: Keep the following properties commented at the moment. Using bf:contentAccessibility as "container" term for the classes. 40 | # unclear order and grouping of a single property might might need more advanced filter and gui functionality, could be easier with specific properties. 41 | # see also display guides. 42 | 43 | # :accessibilityFeature a owl:ObjectProperty ; 44 | # :category :pending ; 45 | # sdo:rangeIncludes :AccessibilityFeature ; 46 | # owl:equivalentProperty sdo:accessibilityFeature . 47 | 48 | # :accessibilityHazard a owl:ObjectProperty ; 49 | # :category :pending ; 50 | # sdo:rangeIncludes :AccessibilityHazard ; 51 | # rdfs:subPropertyOf sdo:accessibilityHazard . 52 | 53 | # :accessMode a owl:ObjectProperty ; 54 | # :category :pending ; 55 | # sdo:rangeIncludes :AccessMode ; 56 | # rdfs:subPropertyOf sdo:accessMode . 57 | 58 | # :accessModeSufficient a owl:ObjectProperty ; 59 | # :category :pending ; 60 | # sdo:rangeIncludes :AccessModeSufficient ; 61 | # rdfs:subPropertyOf sdo:accessModeSufficient . 62 | 63 | # :accessibilitySummary a owl:DatatypeProperty ; 64 | # :category :pending ; 65 | # rdfs:subPropertyOf sdo:accessibilitySummary . 66 | 67 | ## 68 | # ContentAccessibility subClasses for a11y-terms, aligning with Schema properties: 69 | 70 | :AccessibilityFeature a owl:Class ; 71 | :category :pending ; 72 | vs:term_status "unstable" ; 73 | rdfs:label "Accessibility feature"@en ; 74 | rdfs:subClassOf :ContentAccessibility . 75 | 76 | :AccessibilityHazard a owl:Class ; 77 | :category :pending ; 78 | vs:term_status "unstable" ; 79 | rdfs:label "Accessibility hazard"@en ; 80 | rdfs:subClassOf :ContentAccessibility . 81 | 82 | :AccessibilitySummary a owl:Class ; 83 | :category :pending ; 84 | vs:term_status "unstable" ; 85 | rdfs:label "Accessibility summary"@en ; 86 | rdfs:subClassOf :ContentAccessibility . 87 | 88 | :AccessMode a owl:Class ; 89 | :category :pending ; 90 | vs:term_status "unstable" ; 91 | rdfs:label "Access mode"@en ; 92 | rdfs:subClassOf :ContentAccessibility . 93 | 94 | :AccessModeSufficient a owl:Class ; 95 | :category :pending ; 96 | vs:term_status "unstable" ; 97 | rdfs:label "Access mode sufficient"@en ; 98 | rdfs:subClassOf :ContentAccessibility . 99 | 100 | #################### 101 | # ADDITIONAL TERMS # 102 | #################### 103 | 104 | :certifiedBy a owl:ObjectProperty ; 105 | :category :pending ; 106 | rdfs:range :Agent ; #literal in epub spec but hass several properties indicating more information on certifier. 107 | :seeAlso . 108 | 109 | 110 | # Not really a11y-terms but used in EPUB context, might move later but pending for now: 111 | # TODO: Possible mapping coordination with Bibframe-terms like descriptionConventions. 112 | 113 | :conformsTo a owl:ObjectProperty ; 114 | :category :pending ; 115 | sdo:rangeIncludes :Standard ; 116 | owl:equivalentProperty dc:conformsTo . 117 | 118 | :Standard a owl:Class ; 119 | :category :pending ; 120 | owl:equivalentClass dc:Standard . 121 | -------------------------------------------------------------------------------- /source/vocab/bf-map.ttl: -------------------------------------------------------------------------------- 1 | @prefix rdfs: . 2 | @prefix owl: . 3 | @prefix sdo: . 4 | @prefix bf2: . 5 | @prefix kbv: . 6 | 7 | #kbv:Record owl:equivalentClass kbv:AdminMetadata . 8 | 9 | kbv:created owl:equivalentProperty bf2:creationDate . 10 | kbv:modified owl:equivalentProperty bf2:changeDate . 11 | 12 | kbv:identifiedBy rdfs:domain kbv:Creation . 13 | kbv:language rdfs:domain kbv:Creation . 14 | kbv:partOfSeries rdfs:domain kbv:Creation . 15 | kbv:referencedBy rdfs:domain kbv:Creation . 16 | kbv:references rdfs:domain kbv:Creation . 17 | kbv:hasEquivalent rdfs:domain kbv:Creation . 18 | kbv:accompanies rdfs:domain kbv:Creation . 19 | kbv:accompaniedBy rdfs:domain kbv:Creation . 20 | kbv:summary rdfs:domain kbv:Endeavour . 21 | 22 | kbv:hasTitle owl:equivalentProperty bf2:title . 23 | kbv:contentType owl:equivalentProperty bf2:content . 24 | kbv:mediaType owl:equivalentProperty bf2:media . 25 | kbv:carrierType owl:equivalentProperty bf2:carrier . 26 | kbv:ContentType owl:equivalentClass bf2:Content . 27 | kbv:MediaType owl:equivalentClass bf2:Media . 28 | kbv:CarrierType owl:equivalentClass bf2:Carrier . 29 | 30 | kbv:issuanceType owl:equivalentProperty bf2:issuance . 31 | kbv:IssuanceType owl:equivalentClass bf2:Issuance . 32 | 33 | kbv:hasNotation owl:equivalentProperty bf2:notation . 34 | kbv:hasNote owl:equivalentProperty bf2:note . 35 | 36 | kbv:creditsNote owl:equivalentProperty bf2:credits . 37 | kbv:awardsNote owl:equivalentProperty bf2:awards . 38 | 39 | kbv:isPartOf owl:equivalentProperty bf2:partOf . 40 | 41 | kbv:hasPart owl:equivalentProperty bf2:hasPart ; 42 | rdfs:domain kbv:Resource ; # NOTE: loosens implied bf2 domain 43 | rdfs:range kbv:Resource . # NOTE: loosens implied bf2 range 44 | 45 | kbv:relatedTo owl:equivalentProperty bf2:relatedTo ; 46 | rdfs:domain kbv:Resource ; # NOTE: loosens implied bf2 domain 47 | rdfs:range kbv:Resource . # NOTE: loosens implied bf2 range 48 | 49 | kbv:CODEN a rdfs:Datatype; owl:equivalentClass bf2:Coden . 50 | kbv:DOI a rdfs:Datatype; owl:equivalentClass bf2:Doi . 51 | kbv:EAN a rdfs:Datatype; owl:equivalentClass bf2:Ean . 52 | kbv:EIDR a rdfs:Datatype; owl:equivalentClass bf2:Eidr . 53 | kbv:GTIN14 a rdfs:Datatype; owl:equivalentClass bf2:Gtin14Number . 54 | kbv:ISAN a rdfs:Datatype; owl:equivalentClass bf2:Isan . 55 | kbv:ISBN a rdfs:Datatype; owl:equivalentClass bf2:Isbn . 56 | kbv:ISMN a rdfs:Datatype; owl:equivalentClass bf2:Ismn . 57 | kbv:ISNI a rdfs:Datatype; owl:equivalentClass bf2:Isni . 58 | kbv:ISO a rdfs:Datatype; owl:equivalentClass bf2:Iso . 59 | kbv:ISRC a rdfs:Datatype; owl:equivalentClass bf2:Isrc . 60 | kbv:ISSN a rdfs:Datatype; owl:equivalentClass bf2:Issn . 61 | kbv:ISSNL a rdfs:Datatype; owl:equivalentClass bf2:IssnL . 62 | kbv:ISTC a rdfs:Datatype; owl:equivalentClass bf2:Istc . 63 | kbv:ISWC a rdfs:Datatype; owl:equivalentClass bf2:Iswc . 64 | kbv:LCCN a rdfs:Datatype; owl:equivalentClass bf2:Lccn . 65 | kbv:NBN a rdfs:Datatype; owl:equivalentClass bf2:Nbn . 66 | kbv:SICI a rdfs:Datatype; owl:equivalentClass bf2:Sici . 67 | kbv:STRN a rdfs:Datatype; owl:equivalentClass bf2:Strn . 68 | kbv:UPC a rdfs:Datatype; owl:equivalentClass bf2:Upc . 69 | kbv:URN a rdfs:Datatype; owl:equivalentClass bf2:Urn . 70 | 71 | 72 | # TODO: Remove from kbv 73 | # bf2:shelfMark as ObjectProperty 74 | # bf2:shelfMarkDdc 75 | # bf2:shelfMarkLcc 76 | # bf2:shelfMarkUdc 77 | # bf2:shelfMarkNlm 78 | -------------------------------------------------------------------------------- /source/vocab/bf-to-kbv-base.rq: -------------------------------------------------------------------------------- 1 | prefix rdfs: 2 | prefix owl: 3 | prefix bf: 4 | prefix kbv: 5 | prefix sdo: 6 | 7 | construct { 8 | 9 | ?kbvterm a ?type ; 10 | ?maplink ?bfterm ; 11 | rdfs:label ?kbvlabel_en ; 12 | rdfs:subClassOf ?kbvbaseclass ; 13 | rdfs:domain ?kbvdomain ; 14 | sdo:domainIncludes ?kbvdomainincludes ; 15 | rdfs:range ?kbvrange ; 16 | sdo:rangeIncludes ?kbvrangeincludes ; 17 | rdfs:isDefinedBy kbv: . 18 | 19 | } where { 20 | 21 | # General 22 | 23 | ?bfterm a ?type . 24 | filter ((?bfterm != bf:) && strstarts(str(?bfterm), str(bf:))) 25 | ?bfterm rdfs:label ?bflabel . 26 | 27 | bind(if(?type = owl:Class, owl:equivalentClass, owl:equivalentProperty) as ?maplink) 28 | 29 | optional { 30 | ?mappedterm ?maplink ?bfterm 31 | optional { 32 | ?mappedterm rdfs:label ?mappedlabel . 33 | filter(langMatches(?mappedlabel, 'en')) 34 | } 35 | } 36 | 37 | bind(coalesce(?mappedterm, 38 | IRI(concat(replace(str(?bfterm), str(bf:), str(kbv:))))) 39 | as ?kbvterm) 40 | 41 | bind(coalesce(?mappedlabel, strlang(?bflabel, 'en')) as ?kbvlabel_en) 42 | 43 | 44 | # Class 45 | 46 | optional { 47 | ?bfterm rdfs:subClassOf ?baseclass . 48 | 49 | optional { ?mappedclass owl:equivalentClass ?baseclass } 50 | 51 | bind(coalesce(?mappedclass, 52 | IRI(concat(replace(str(?baseclass), str(bf:), str(kbv:))))) 53 | as ?kbvbaseclass) 54 | } 55 | 56 | 57 | # Property 58 | 59 | # map domain 60 | optional { 61 | ?bfterm rdfs:domain ?domain 62 | optional { ?mappeddomain owl:equivalentClass ?domain } 63 | 64 | bind(coalesce(?mappeddomain, 65 | IRI(concat(replace(str(?domain), str(bf:), str(kbv:))))) 66 | as ?kbvdomain) 67 | } 68 | optional { 69 | filter not exists { ?kbvterm rdfs:domain [] } 70 | filter exists { ?bfterm rdfs:comment "Used with Work, Instance or Item" } 71 | bind(kbv:Endeavour as ?kbvdomain) 72 | } 73 | optional { 74 | filter not exists { ?kbvterm rdfs:domain [] } 75 | filter exists { ?bfterm rdfs:comment "Used with Work or Instance" } 76 | bind(kbv:Creation as ?kbvdomain) 77 | } 78 | 79 | optional { 80 | filter not exists { ?kbvterm rdfs:domain [] } 81 | filter exists { ?bfterm rdfs:comment "Used with Unspecified" } 82 | bind(rdfs:Resource as ?kbvdomain) 83 | } 84 | # map domainIncludes 85 | optional { 86 | filter not exists { ?kbvterm rdfs:domain [] } 87 | filter exists { ?bfterm rdfs:comment "Suggested use - With Work, Instance or Item" } 88 | bind(kbv:Endeavour as ?kbvdomainincludes) 89 | } 90 | optional { 91 | filter not exists { ?kbvterm rdfs:domain [] } 92 | filter exists { ?bfterm rdfs:comment "Suggested use - With Work or Instance" } 93 | bind(kbv:Creation as ?kbvdomainincludes) 94 | } 95 | # map range 96 | optional { 97 | ?bfterm rdfs:range ?range 98 | optional { ?mappedrange owl:equivalentClass ?range } 99 | bind(coalesce(?mappedrange, 100 | IRI(concat(replace(str(?range), str(bf:), str(kbv:))))) 101 | as ?kbvrange) 102 | } 103 | optional { 104 | filter not exists { ?kbvterm rdfs:range [] } 105 | filter exists { ?bfterm rdfs:comment "Expected value Work, Instance or Item" } 106 | bind(kbv:Endeavour as ?kbvrange) 107 | } 108 | optional { 109 | filter not exists { ?kbvterm rdfs:range [] } 110 | filter exists { ?bfterm rdfs:comment "Expected value Work or Instance" } 111 | bind(kbv:Creation as ?kbvrange) 112 | } 113 | # map rangeIncludes 114 | optional { 115 | filter not exists { ?kbvterm rdfs:range [] } 116 | filter exists { ?bfterm rdfs:comment "Suggested value - Work, Instance or Item" } 117 | bind(kbv:Endeavour as ?kbvrangeincludes) 118 | } 119 | optional { 120 | filter not exists { ?kbvterm rdfs:range [] } 121 | filter exists { ?bfterm rdfs:comment "Suggested value - Work or Instance" } 122 | bind(kbv:Creation as ?kbvrangeincludes) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /source/vocab/check-bases.rq: -------------------------------------------------------------------------------- 1 | prefix rdfs: 2 | prefix owl: 3 | prefix dct: 4 | prefix sdo: 5 | prefix : 6 | 7 | construct { 8 | 9 | ?check a :Warning; :message ?msg; :implicated (?base ?term) . 10 | 11 | } where { 12 | 13 | { 14 | ?term (rdfs:subClassOf|owl:equivalentClass) ?base . 15 | filter not exists { 16 | { ?base a rdfs:Class } union { ?base a owl:Class } union { ?base a rdfs:Datatype } 17 | } 18 | bind("Unknown base class {} for term {}"@en as ?msg) 19 | 20 | } union { 21 | ?term (rdfs:subPropertyOf|owl:equivalentProperty) ?base . 22 | filter not exists { 23 | { ?base a rdf:Property } 24 | union { ?base a owl:DatatypeProperty } 25 | union { ?base a owl:ObjectProperty } 26 | union { ?base a owl:SymmetricProperty } 27 | union { ?base a owl:TransitiveProperty } 28 | } 29 | bind("Unknown base property {} for term {}"@en as ?msg) 30 | 31 | } union { 32 | ?term (rdfs:domain|sdo:domainIncludes) ?base . 33 | filter not exists { 34 | { ?base a rdfs:Class } union { ?base a owl:Class } 35 | } 36 | bind("Unknown domain {} for term {}"@en as ?msg) 37 | 38 | } union { 39 | ?term (rdfs:range|sdo:rangeIncludes) ?base . 40 | filter not exists { 41 | { ?base a rdfs:Class } union { ?base a owl:Class } 42 | } 43 | bind("Unknown range {} for term {}"@en as ?msg) 44 | } 45 | 46 | bind(bnode(str(?base)) as ?check) 47 | 48 | filter(isIRI(?term) && isIRI(?base) 49 | && strStarts(str(?base), replace(str(?term), '[^#/:]+$', ''))) 50 | 51 | } 52 | -------------------------------------------------------------------------------- /source/vocab/external-labels.ttl: -------------------------------------------------------------------------------- 1 | prefix rdfs: 2 | prefix owl: 3 | prefix schema: 4 | prefix ptg: 5 | 6 | rdfs:subClassOf rdfs:label "Har basklass"@sv, "Sub class of"@en ; 7 | owl:inverseOf [ rdfs:label "Basklass för"@sv, "Base class of"@en ] . 8 | 9 | rdfs:subPropertyOf rdfs:label "Har basegenskap"@sv, "Sub property of"@en ; 10 | owl:inverseOf [ rdfs:label "Basegenskap för"@sv, "Base property of"@en ] . 11 | 12 | owl:inverseOf rdfs:label "Invers av"@sv, "Inverse of"@en . 13 | 14 | rdfs:isDefinedBy rdfs:label "Definieras av"@sv, "Is defined by"@en . 15 | 16 | ptg:abstract rdfs:label "Abstrakt"@sv, "Abstract"@en . 17 | 18 | owl:equivalentClass rdfs:label "Motsvarar"@sv, "Equivalent class"@en . 19 | 20 | rdfs:domain rdfs:label "Förekommer på"@sv, "Domain"@en . 21 | 22 | schema:domainIncludes rdfs:label "Kan förekomma på"@sv, "Domain includes"@en . 23 | 24 | rdfs:range rdfs:label "Pekar på"@sv, "Range"@en . 25 | 26 | schema:rangeIncludes rdfs:label "Kan peka på"@sv, "Range includes"@en . 27 | 28 | owl:equivalentProperty rdfs:label "Motsvarar"@sv, "Equivalent property"@en . 29 | -------------------------------------------------------------------------------- /source/vocab/files-packages-representations.ttl: -------------------------------------------------------------------------------- 1 | @prefix rdfs: . 2 | @prefix owl: . 3 | @prefix xsd: . 4 | @prefix skos: . 5 | @prefix prov: . 6 | @prefix sdo: . 7 | @prefix dc: . 8 | @prefix bf2: . 9 | @prefix premis3: . 10 | @prefix iiifpres3: . 11 | #prefix edm: 12 | @prefix relsubtype: . 13 | @prefix : . 14 | 15 | ## 16 | # PREMIS 3.0 17 | 18 | :Creation rdfs:subClassOf premis3:IntellectualEntity . 19 | 20 | :Representation a owl:Class ; 21 | rdfs:label "Representation"@en, "Representation"@sv ; 22 | owl:equivalentClass premis3:Representation ; 23 | rdfs:subClassOf :Embodiment ; 24 | rdfs:subClassOf [ a owl:Restriction; 25 | owl:onProperty :hasPart; owl:allValuesFrom :Representation ] ; 26 | rdfs:subClassOf [ a owl:Restriction; 27 | owl:onProperty :includes; owl:allValuesFrom [ owl:disjointWith :Representation ] ] ; 28 | rdfs:subClassOf [ a owl:Restriction; 29 | owl:onProperty :includedIn; owl:allValuesFrom [ owl:disjointWith :Representation ] ] . 30 | 31 | :Bitstream a owl:Class ; 32 | rdfs:label "Bitstream"@en, "Bitström"@sv ; 33 | owl:equivalentClass premis3:Bitstream ; 34 | rdfs:subClassOf :MediaObject . 35 | 36 | :File a owl:Class ; 37 | rdfs:label "File"@en, "Fil"@sv ; 38 | owl:equivalentClass premis3:File ; 39 | rdfs:subClassOf :MediaObject ; 40 | rdfs:subClassOf [ a owl:Restriction ; 41 | owl:onProperty :hasPart; owl:allValuesFrom :File ] ; 42 | rdfs:subClassOf [ a owl:Restriction; 43 | owl:onProperty :includes; owl:allValuesFrom [ owl:disjointWith :File ] ] ; 44 | rdfs:subClassOf [ a owl:Restriction; 45 | owl:onProperty :includedIn; owl:allValuesFrom [ owl:disjointWith :File ] ] . 46 | 47 | :representationOf a owl:ObjectProperty ; 48 | rdfs:label "representation of"@en, "representation av"@sv ; 49 | sdo:domainIncludes :MediaObject, :Representation ; 50 | rdfs:range :Creation ; 51 | owl:equivalentProperty relsubtype:rep ; # NOTE: PREMIS calls it represents (the label of rep). 52 | owl:inverseOf :hasRepresentation ; 53 | rdfs:subPropertyOf sdo:encodesCreativeWork, :formatOf . 54 | 55 | :hasRepresentation a owl:ObjectProperty ; 56 | rdfs:label "has representation"@en, "har representation"@sv ; 57 | rdfs:domain :Creation ; 58 | sdo:rangeIncludes :MediaObject, :Representation ; 59 | owl:equivalentProperty relsubtype:isr ; # NOTE: PREMIS calls it isRepresentedBy (the label of isr). 60 | owl:inverseOf :representationOf ; 61 | rdfs:subPropertyOf :associatedMedia . 62 | 63 | :hasIIIFManifest a owl:ObjectProperty ; 64 | rdfs:label "has manifest"@en, "har manifest"@sv ; 65 | rdfs:domain :Document ; 66 | sdo:rangeIncludes iiifpres3:Manifest . 67 | 68 | :includes a owl:ObjectProperty ; 69 | rdfs:label "includes"@en, "inkluderar"@sv ; 70 | sdo:domainIncludes :FilePackage, :MediaObject, :Representation ; 71 | sdo:rangeIncludes :MediaObject, :Representation ; 72 | owl:equivalentProperty relsubtype:inc ; # TODO: super-properties of relsubtype:inc instead? 73 | owl:inverseOf :includedIn ; 74 | rdfs:subPropertyOf :hasPart . 75 | 76 | :includedIn a owl:ObjectProperty ; 77 | rdfs:label "included in"@en, "inkluderad i"@sv ; 78 | sdo:domainIncludes :MediaObject, :Representation ; 79 | sdo:rangeIncludes :MediaObject, :Representation ; 80 | owl:equivalentProperty relsubtype:isi ; # TODO: super-properties of relsubtype:isi instead? 81 | owl:inverseOf :includes ; 82 | rdfs:subPropertyOf :isPartOf . 83 | 84 | :encodingFormat a owl:ObjectProperty ; 85 | rdfs:label "encoding format"@en, "kodningsformat"@sv ; 86 | rdfs:domain :MediaObject ; 87 | rdfs:range :EncodingFormat ; 88 | owl:equivalentProperty sdo:encodingFormat ; 89 | rdfs:subPropertyOf :digitalCharacteristic, :format . 90 | 91 | :checksum a owl:DatatypeProperty ; 92 | rdfs:label "checksum"@en, "kontrollsumma"@sv ; 93 | rdfs:domain :File ; 94 | rdfs:range :Checksum ; 95 | rdfs:subPropertyOf premis3:fixity . 96 | 97 | :Checksum a rdfs:Datatype, owl:Class ; rdfs:subClassOf premis3:Fixity ; 98 | rdfs:label "Checksum"@en, "Kontrollsumma"@sv . 99 | 100 | :MD5 a rdfs:Datatype; rdfs:subClassOf :Checksum . 101 | :SHA256 a rdfs:Datatype; rdfs:subClassOf :Checksum . 102 | 103 | :fileName a owl:DatatypeProperty ; 104 | rdfs:label "filename"@en, "filnamn"@sv ; 105 | rdfs:domain :File ; 106 | rdfs:range xsd:string ; 107 | owl:equivalentProperty premis3:originalName ; 108 | rdfs:subPropertyOf :label . 109 | 110 | :contentSize a owl:DatatypeProperty ; 111 | rdfs:label "content size"@en, "innehållsstorlek"@sv ; 112 | rdfs:domain :MediaObject ; 113 | #rdfs:range xsd:integer ; NOTE: Can we be sure data always is in the form of an integer? 114 | skos:scopeNote "The value is an integer representing the file size in bytes, or a qualified string constisting of an integer followed by a conventional unit symbol (KB, MB, GB, TB)."@en ; 115 | owl:equivalentProperty premis3:size, sdo:contentSize . 116 | 117 | ## 118 | # New terms 119 | 120 | :FilePackage a owl:Class ; 121 | rdfs:label "File package"@en, "Filpaket"@sv ; 122 | rdfs:subClassOf :Document; 123 | skos:broader :Dataset, :MediaObject . # TODO: broader dcat:Distribution? 124 | 125 | :hasFilePackage a owl:ObjectProperty ; 126 | rdfs:label "has file package"@en, "har filpaket"@sv ; 127 | sdo:domainIncludes :Document ; 128 | rdfs:range :FilePackage . 129 | 130 | :hasIntellectualEntity a owl:ObjectProperty ; 131 | rdfs:label "has intellectual entity"@en, "har intellektuell entitet"@sv ; 132 | sdo:domainIncludes :Document ; 133 | rdfs:range :Creation . 134 | 135 | :width a owl:DatatypeProperty ; 136 | rdfs:label "width"@en, "bredd"@sv ; 137 | sdo:domainIncludes :MediaObject ; # TODO: broaden domain later to also apply on Manifest and physical objects 138 | owl:equivalentProperty sdo:width . 139 | 140 | :height a owl:DatatypeProperty ; 141 | rdfs:label "height"@en, "höjd"@sv ; 142 | sdo:domainIncludes :MediaObject ; # TODO: broaden domain later to also apply on Manifest and physical objects 143 | owl:equivalentProperty sdo:height . 144 | -------------------------------------------------------------------------------- /source/vocab/libris-search-experimental.ttl: -------------------------------------------------------------------------------- 1 | @prefix rdfs: . 2 | @prefix owl: . 3 | @prefix skos: . 4 | @prefix : . 5 | 6 | @prefix rdf: . 7 | @prefix xsd: . 8 | 9 | :LibrisQueryCode a rdfs:Datatype . 10 | 11 | :author a owl:ObjectProperty ; 12 | rdfs:label "författare"@sv, "author"@en ; 13 | :category :shorthand, :pending ; 14 | owl:equivalentProperty ; 15 | rdfs:subPropertyOf :contributor ; 16 | rdfs:range :Agent ; 17 | skos:notation "FÖRF"^^:LibrisQueryCode ; 18 | owl:propertyChainAxiom ( 19 | [ rdfs:subPropertyOf :contribution ; rdfs:range [ rdfs:subClassOf [ a owl:Restriction ; 20 | owl:onProperty :role ; 21 | owl:hasValue ] ] ] 22 | :agent 23 | ) . 24 | 25 | :isbn rdfs:domain :Instance . # See ./details.ttl for full definition of :isbn 26 | 27 | :yearPublished rdfs:label "utgivningsår"@sv, "year of publication"@en ; # See ./details.ttl for full definition of :yearPublished 28 | rdfs:domain :Instance ; 29 | skos:notation "ÅR"^^:LibrisQueryCode . 30 | 31 | :language skos:notation "SPRÅK"^^:LibrisQueryCode . # See ./relations.ttl for full definition of :language 32 | 33 | :itemHeldBy a owl:ObjectProperty ; 34 | rdfs:label "Bibliotek"@sv, "Library"@en ; 35 | :category :shorthand, :pending ; 36 | rdfs:domain :Instance ; 37 | rdfs:range :Item ; 38 | owl:propertyChainAxiom ( :hasItem :heldBy ) . 39 | 40 | :instanceOfType a owl:ObjectProperty ; 41 | :category :shorthand, :pending ; 42 | rdfs:domain :Instance ; 43 | owl:propertyChainAxiom ( :instanceOf rdf:type ) . 44 | 45 | :hasInstanceType a owl:ObjectProperty ; 46 | rdfs:label "format"@sv, "format"@en ; 47 | skos:notation "FORMAT"^^:LibrisQueryCode ; 48 | :category :shorthand, :pending ; 49 | rdfs:domain :Work ; 50 | owl:propertyChainAxiom ( :hasInstance rdf:type ) . 51 | -------------------------------------------------------------------------------- /source/vocab/update.rq: -------------------------------------------------------------------------------- 1 | prefix rdfs: 2 | prefix : 3 | 4 | insert { 5 | ?term rdfs:isDefinedBy : 6 | } where { 7 | ?term ?p ?o 8 | filter ((?term != :) && strstarts(str(?term), str(:))) 9 | }; 10 | 11 | # Remove uninteresting domain/range if there are specific ones. 12 | delete { 13 | ?s ?p rdfs:Resource 14 | } where { 15 | ?s ?p ?o 16 | filter(?o != rdfs:Resource && strstarts(str(?o), str(:))) 17 | filter(?p in (rdfs:subClassOf, rdfs:domain, rdfs:range)) 18 | }; 19 | 20 | # Remap domain/range if there are specific suggestions. 21 | delete { 22 | ?s ?domainOrRange rdfs:Resource 23 | # TODO: this is formally correct, but "mutes" the includes in the cataloguing tool... 24 | #} insert { 25 | # ?s ?domainOrRange :Resource 26 | } where { 27 | ?s ?includesRelation ?o 28 | filter(?o != rdfs:Resource && strstarts(str(?o), str(:))) 29 | 30 | values (?domainOrRange ?includesRelation) { 31 | (rdfs:domain sdo:domainIncludes) 32 | (rdfs:range sdo:rangeIncludes) 33 | } 34 | }; 35 | 36 | # remove redundant base classes 37 | delete { 38 | ?c rdfs:subClassOf ?a . 39 | } where { 40 | ?c rdfs:subClassOf / rdfs:subClassOf+ ?a . 41 | }; 42 | 43 | # remove disputed work sublasses 44 | delete { 45 | ?notawork rdfs:subClassOf :Work . 46 | } where { 47 | values (?notawork) { 48 | (:Manuscript) 49 | (:Collection) 50 | (:Integrating) 51 | (:Monograph) 52 | (:Serial) 53 | (:Series) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /source/vocab/updateResource.rq: -------------------------------------------------------------------------------- 1 | prefix rdfs: 2 | prefix owl: 3 | prefix : 4 | 5 | insert { 6 | ?term rdfs:subClassOf :Resource 7 | 8 | } where { 9 | ?term a owl:Class 10 | 11 | # Add Resource where subClassOf is missing (Is this part redundant? Enough with the minus clause?) 12 | optional {?term rdfs:subClassOf ?subClassOf} 13 | filter(!bound(?subClassOf)) 14 | 15 | # Don't add subClassOf if term already is :Resource 16 | filter(?term != :Resource) 17 | 18 | # Don't add if ?subClassOf contains refs that starts with 19 | MINUS { 20 | ?term rdfs:subClassOf ?baseClassUri 21 | filter ((?baseClassUri != :) && strstarts(str(?baseClassUri), str(:))) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sys/context/base.jsonld: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "skos": "http://www.w3.org/2004/02/skos/core#", 4 | "void": "http://rdfs.org/ns/void#", 5 | "foaf": "http://xmlns.com/foaf/0.1/", 6 | "dc": "http://purl.org/dc/terms/", 7 | "hydra": "http://www.w3.org/ns/hydra/core#", 8 | "@vocab": "https://id.kb.se/vocab/", 9 | "marc": "https://id.kb.se/marc/", 10 | "bibdb": "https://id.kb.se/ns/bibdb/", 11 | 12 | "@import": "./shared.jsonld", 13 | 14 | "value": "rdf:value", 15 | "sameAs": {"@id": "owl:sameAs", "@container": "@set"}, 16 | 17 | "Concept": "skos:Concept", 18 | "ConceptCollection": "skos:Collection", 19 | "ConceptScheme": "skos:ConceptScheme", 20 | 21 | "label": "rdfs:label", 22 | "comment": "rdfs:comment", 23 | "prefLabel": "skos:prefLabel", 24 | "altLabel": "skos:altLabel", 25 | "hiddenLabel": "skos:hiddenLabel", 26 | "code": "skos:notation", 27 | "note": "skos:note", 28 | "example": "skos:example", 29 | "definition": "skos:definition", 30 | "scopeNote": "skos:scopeNote", 31 | "changeNote": "skos:changeNote", 32 | "editorialNote": "skos:editorialNote", 33 | "historyNote": "skos:historyNote", 34 | "inDataset": "void:inDataset", 35 | 36 | "title": "dc:title", 37 | "alternative": "dc:alternative", 38 | "description": "dc:description", 39 | "license": "dc:license", 40 | 41 | "isPrimaryTopicOf": "foaf:isPrimaryTopicOf", 42 | "homepage": "foaf:homepage", 43 | "page": "foaf:page", 44 | 45 | "broadMatch": {"@id": "skos:broadMatch", "@container": "@set"}, 46 | "broader": {"@id": "skos:broader", "@container": "@set"}, 47 | "closeMatch": {"@id": "skos:closeMatch", "@container": "@set"}, 48 | "exactMatch": {"@id": "skos:exactMatch", "@container": "@set"}, 49 | "inScheme": {"@id": "skos:inScheme"}, 50 | "narrowMatch": {"@id": "skos:narrowMatch", "@container": "@set"}, 51 | "relatedMatch": {"@id": "skos:relatedMatch", "@container": "@set"}, 52 | "narrower": {"@id": "skos:narrower", "@container": "@set"}, 53 | "related": {"@id": "skos:related", "@container": "@set"}, 54 | "hasTopConcept": {"@id": "skos:hasTopConcept", "@container": "@set"}, 55 | 56 | "hasVersion": {"@id": "dc:hasVersion"}, 57 | "isVersionOf": {"@id": "dc:isVersionOf"}, 58 | "isReplacedBy": {"@id": "dc:isReplacedBy"}, 59 | "language": {"@id": "dc:language"}, 60 | 61 | "GenerationProcess": "http://id.loc.gov/ontologies/bibframe/GenerationProcess", 62 | "Country": "sdo:Country", 63 | "Language": "sdo:Language", 64 | "Role": "http://id.loc.gov/ontologies/bibframe/Role", 65 | "ItemAvailability": "sdo:ItemAvailability", 66 | 67 | "ContentType": "http://id.loc.gov/ontologies/bibframe/Content", 68 | "MediaType": "http://id.loc.gov/ontologies/bibframe/Media", 69 | "CarrierType": "http://id.loc.gov/ontologies/bibframe/Carrier", 70 | "IssuanceType": "http://id.loc.gov/ontologies/bibframe/Issuance" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /sys/context/keywords.jsonld: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "type": "@type", 4 | "id": "@id", 5 | "lang": "@language", 6 | "val": "@value", 7 | "dt": "@datatype" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /sys/context/ns.jsonld: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 4 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 5 | "owl": "http://www.w3.org/2002/07/owl#", 6 | "skos": "http://www.w3.org/2004/02/skos/core#", 7 | "xsd": "http://www.w3.org/2001/XMLSchema#", 8 | "dc": "http://purl.org/dc/terms/", 9 | "schema": "http://schema.org/", 10 | "kbv": "https://id.kb.se/vocab/" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sys/context/shared.jsonld: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 4 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 5 | "owl": "http://www.w3.org/2002/07/owl#", 6 | "xsd": "http://www.w3.org/2001/XMLSchema#", 7 | "sdo": "http://schema.org/", 8 | "jsonld": "http://www.w3.org/ns/json-ld#", 9 | "fresnel": "http://www.w3.org/2004/09/fresnel#", 10 | "kbv": "https://id.kb.se/vocab/", 11 | 12 | "Property": "rdf:Property", 13 | "Datatype": "rdfs:Datatype", 14 | "Ontology": "owl:Ontology", 15 | "Class": "owl:Class", 16 | "ObjectProperty": "owl:ObjectProperty", 17 | "DatatypeProperty": "owl:DatatypeProperty", 18 | "FunctionalProperty": "owl:FunctionalProperty", 19 | "TransitiveProperty": "owl:TransitiveProperty", 20 | "Restriction": "owl:Restriction", 21 | 22 | "subClassOf": {"@id": "rdfs:subClassOf", "@container": "@set"}, 23 | "subPropertyOf": {"@id": "rdfs:subPropertyOf", "@container": "@set"}, 24 | "equivalentClass": {"@id": "owl:equivalentClass", "@container": "@set"}, 25 | "equivalentProperty": {"@id": "owl:equivalentProperty", "@container": "@set"}, 26 | "imports": {"@id": "owl:imports", "@container": "@set"}, 27 | "unionOf": {"@id": "owl:unionOf", "@container": "@list"}, 28 | "intersectionOf": {"@id": "owl:intersectionOf", "@container": "@list"}, 29 | "propertyChainAxiom": {"@id": "owl:propertyChainAxiom", "@container": "@list"}, 30 | "onProperty": {"@id": "owl:onProperty"}, 31 | "inverseOf": {"@id": "owl:inverseOf"}, 32 | "allValuesFrom": {"@id": "owl:allValuesFrom"}, 33 | "someValuesFrom": {"@id": "owl:someValuesFrom"}, 34 | "hasValue": {"@id": "owl:hasValue"}, 35 | "domain": {"@id": "rdfs:domain", "@container": "@set"}, 36 | "range": {"@id": "rdfs:range", "@container": "@set"}, 37 | "domainIncludes": {"@id": "sdo:domainIncludes", "@container": "@set"}, 38 | "rangeIncludes": {"@id": "sdo:rangeIncludes", "@container": "@set"}, 39 | "isDefinedBy": {"@id": "rdfs:isDefinedBy"}, 40 | "seeAlso": {"@id": "rdfs:seeAlso", "@container": "@set"}, 41 | "abstract": "http://protege.stanford.edu/plugins/owl/protege#abstract", 42 | "termGroup": "http://purl.org/vocab/vann/termGroup", 43 | "term_status": "http://www.w3.org/2003/06/sw-vocab-status/ns#term_status", 44 | "lensGroup": "fresnel:group", 45 | 46 | "isMemberOfRelation": {"@type": "@vocab"}, 47 | "memberClass": {"@type": "@vocab", "@container": "@set"}, 48 | "slugProperty": {"@type": "@vocab"}, 49 | "administeredBy": {"@container": "@set"}, 50 | 51 | "labelByLang": {"@id": "label", "@container": "@language"}, 52 | "commentByLang": {"@id": "comment", "@container": "@language"}, 53 | "prefLabelByLang": {"@id": "prefLabel", "@container": "@language"}, 54 | "altLabelByLang": {"@id": "altLabel", "@container": "@language"}, 55 | "hiddenLabelByLang": {"@id": "hiddenLabel", "@container": "@language"}, 56 | "noteByLang": {"@id": "note", "@container": "@language"}, 57 | "definitionByLang": {"@id": "definition", "@container": "@language"}, 58 | "scopeNoteByLang": {"@id": "scopeNote", "@container": "@language"}, 59 | "changeNoteByLang": {"@id": "changeNote", "@container": "@language"}, 60 | "historyNoteByLang": {"@id": "historyNote", "@container": "@language"}, 61 | "titleByLang": {"@id": "title", "@container": "@language"}, 62 | "descriptionByLang": {"@id": "description", "@container": "@language"}, 63 | 64 | "mainTitleByLang": {"@id": "mainTitle", "@container": "@language"}, 65 | "subtitleByLang": {"@id": "subtitle", "@container": "@language"}, 66 | "titleRemainderByLang": {"@id": "titleRemainder", "@container": "@language"}, 67 | "partNumberByLang": {"@id": "partNumber", "@container": "@language"}, 68 | "partNameByLang": {"@id": "partName", "@container": "@language"}, 69 | "responsibilityStatementByLang": {"@id": "responsibilityStatement", "@container": "@language"}, 70 | "editionStatementByLang": {"@id": "editionStatement", "@container": "@language"}, 71 | "seriesStatementByLang": {"@id": "seriesStatement", "@container": "@language"}, 72 | "seriesEnumerationByLang": {"@id": "seriesEnumeration", "@container": "@language"}, 73 | 74 | "familyNameByLang": {"@id": "familyName", "@container": "@language"}, 75 | "givenNameByLang": {"@id": "givenName", "@container": "@language"}, 76 | "nameByLang": {"@id": "name", "@container": "@language"}, 77 | "lifeSpanByLang": {"@id": "lifeSpan", "@container": "@language"}, 78 | 79 | "labelHTML": {"@id": "label", "@type": "rdf:HTML"}, 80 | 81 | "langTag": {"@id": "code", "@type": "BCP47"}, 82 | "langCodeShort": {"@id": "code", "@type": "ISO639-1"}, 83 | "langCode": {"@id": "code", "@type": "ISO639-2"}, 84 | "langCodeBib": {"@id": "code", "@type": "ISO639-2-B"}, 85 | "langCodeTerm": {"@id": "code", "@type": "ISO639-2-T"}, 86 | "langCodeFull": {"@id": "code", "@type": "ISO639-3"}, 87 | "langCodeLibrisLocal": {"@id": "code", "@type": "LibrisLocalLanguageCode"}, 88 | 89 | "librisQueryCode": {"@id": "code", "@type": "LibrisQueryCode"}, 90 | 91 | "bibdb:bibIdSearchUriByLang": {"@id": "bibdb:bibIdSearchUri", "@container": "@language"}, 92 | "bibdb:displayMode": {"@type": "@vocab"} 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /sys/context/target/bibo-w3c.jsonld: -------------------------------------------------------------------------------- 1 | { 2 | "@context": [ 3 | {"bibo": "http://purl.org/ontology/bibo/"}, 4 | {"skos": "http://www.w3.org/2004/02/skos/core#"}, 5 | {"dc": "http://purl.org/dc/terms/"}, 6 | {"dctype": "http://purl.org/dc/dcmitype/"}, 7 | {"prov": "http://www.w3.org/ns/prov#"}, 8 | {"foaf": "http://xmlns.com/foaf/0.1/"}, 9 | {"void": "http://rdfs.org/ns/void#"}, 10 | {"owl": "http://www.w3.org/2002/07/owl#"}, 11 | {"rdfs": "http://www.w3.org/2000/01/rdf-schema#"}, 12 | {"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#"}, 13 | {"xsd": "http://www.w3.org/2001/XMLSchema#"}, 14 | {"ldp": "http://www.w3.org/ns/ldp#"} 15 | ], 16 | "created": "2021-10-18T14:18:25.440Z" 17 | } 18 | -------------------------------------------------------------------------------- /sys/context/target/loc-w3c-sdo.jsonld: -------------------------------------------------------------------------------- 1 | { 2 | "@context": [ 3 | {"bf2": "http://id.loc.gov/ontologies/bibframe/"}, 4 | {"bflc": "http://id.loc.gov/ontologies/bflc/"}, 5 | {"madsrdf": "http://www.loc.gov/mads/rdf/v1#"}, 6 | {"skos": "http://www.w3.org/2004/02/skos/core#"}, 7 | {"dc": "http://purl.org/dc/terms/"}, 8 | {"dctype": "http://purl.org/dc/dcmitype/"}, 9 | {"prov": "http://www.w3.org/ns/prov#"}, 10 | {"sdo": "http://schema.org/"}, 11 | {"bibo": "http://purl.org/ontology/bibo/"}, 12 | {"foaf": "http://xmlns.com/foaf/0.1/"}, 13 | {"void": "http://rdfs.org/ns/void#"}, 14 | {"owl": "http://www.w3.org/2002/07/owl#"}, 15 | {"rdfs": "http://www.w3.org/2000/01/rdf-schema#"}, 16 | {"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#"}, 17 | {"xsd": "http://www.w3.org/2001/XMLSchema#"}, 18 | {"edtf": "http://id.loc.gov/datatypes/edtf/"}, 19 | {"ldp": "http://www.w3.org/ns/ldp#"} 20 | ], 21 | "created": "2021-10-18T14:18:25.440Z" 22 | } 23 | -------------------------------------------------------------------------------- /sys/context/target/sdo-w3c.jsonld: -------------------------------------------------------------------------------- 1 | { 2 | "@context": [ 3 | {"@vocab": "http://schema.org/"}, 4 | {"ldp": "http://www.w3.org/ns/ldp#"}, 5 | {"skos": "http://www.w3.org/2004/02/skos/core#"}, 6 | {"prov": "http://www.w3.org/ns/prov#"}, 7 | {"owl": "http://www.w3.org/2002/07/owl#"}, 8 | {"rdfs": "http://www.w3.org/2000/01/rdf-schema#"}, 9 | {"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#"}, 10 | {"xsd": "http://www.w3.org/2001/XMLSchema#"} 11 | ], 12 | "created": "2021-10-18T13:47:49.626Z" 13 | } 14 | -------------------------------------------------------------------------------- /syscore.py: -------------------------------------------------------------------------------- 1 | import os 2 | from rdflib import Graph, RDF, Namespace 3 | from lxltools.datacompiler import Compiler, last_modified_ms 4 | from lxltools.timeutil import w3c_dtz_to_ms 5 | from urllib.parse import urljoin 6 | 7 | 8 | SCRA = Namespace("http://purl.org/net/schemarama#") 9 | 10 | LIBRIS_BASE = "https://libris.kb.se/" 11 | 12 | ID_BASE = "https://id.kb.se/" 13 | 14 | SCRIPT_DIR = os.path.dirname(__file__) or '.' 15 | 16 | 17 | def _get_repo_version(): 18 | try: 19 | with os.popen( 20 | '(cd {} && git describe --tags)'.format(SCRIPT_DIR) 21 | ) as pipe: 22 | return pipe.read().rstrip() 23 | except: 24 | return None 25 | 26 | 27 | compiler = Compiler(base_dir=SCRIPT_DIR, 28 | dataset_id=LIBRIS_BASE + 'dataset/syscore', 29 | created='2022-11-19T13:09:35.010Z', 30 | tool_id=ID_BASE + 'generator/syscorecompiler', 31 | context='sys/context/base.jsonld', 32 | record_thing_link='mainEntity', 33 | system_base_iri='', 34 | union='syscore.jsonld.lines') 35 | 36 | 37 | @compiler.handler 38 | def contexts(): 39 | contexts_ds_url = urljoin(compiler.dataset_id, 'sys/context') 40 | 41 | context_alias = ID_BASE + 'vocab/context' 42 | 43 | docpath = compiler.path('sys/context/kbv.jsonld') 44 | uripath = ID_BASE + 'sys/context/kbv' 45 | _write_context_record(compiler, docpath, uripath, contexts_ds_url, context_alias) 46 | 47 | root = compiler.path('') 48 | for docpath in compiler.path('sys/context').glob('target/*.jsonld'): 49 | uripath = ID_BASE + str(docpath.relative_to(root).with_suffix('')) 50 | _write_context_record(compiler, docpath, uripath, contexts_ds_url) 51 | 52 | 53 | @compiler.handler 54 | def vocab(): 55 | vocab_base = ID_BASE + 'vocab/' 56 | 57 | graph = compiler.construct(sources=[ 58 | { 59 | 'source': Graph().parse(str(compiler.path('source/vocab/bf-map.ttl')), format='turtle'), 60 | 'dataset': '?' 61 | }, 62 | {"source": "http://id.loc.gov/ontologies/bibframe/"} 63 | ], 64 | query="source/vocab/bf-to-kbv-base.rq") 65 | 66 | for part in compiler.path('source/vocab').glob('**/*.ttl'): 67 | try: 68 | graph.parse(str(part), format='turtle') 69 | except: 70 | print(f"Error in file: {part}") 71 | raise 72 | 73 | graph.update(compiler.path('source/vocab/update.rq').read_text('utf-8')) 74 | graph.update(compiler.path('source/vocab/updateResource.rq').read_text('utf-8')) 75 | 76 | rq = compiler.path('source/vocab/construct-enum-restrictions.rq').read_text('utf-8') 77 | graph += Graph().query(rq).graph 78 | 79 | rq = compiler.path('source/vocab/check-bases.rq').read_text('utf-8') 80 | checks = graph.query(rq).graph 81 | for check, msg in checks.subject_objects(SCRA.message): 82 | print("{}: {}".format( 83 | graph.qname(checks.value(check, RDF.type)).split(':')[-1], 84 | msg.format(*[imp.n3() for imp in 85 | checks.collection(checks.value(check, SCRA.implicated))]) 86 | )) 87 | 88 | for part in compiler.path('source/marc').glob('**/*.ttl'): 89 | graph.parse(str(part), format='turtle') 90 | 91 | graph.parse(str(compiler.path('source/swepub/vocab.ttl')), format='turtle') 92 | 93 | # Clean up generated prefixes 94 | preferred = {} 95 | defaulted = {} 96 | for pfx, uri in graph.store.namespaces(): 97 | if pfx.startswith('default'): 98 | defaulted[uri] = pfx 99 | else: 100 | preferred[uri] = pfx 101 | for default_pfx, uri in defaulted.items(): 102 | if uri in preferred: 103 | graph.namespace_manager.bind(preferred[uri], uri, override=True) 104 | 105 | data = compiler.to_jsonld(graph) 106 | del data['@context'] 107 | 108 | # Put /vocab/* first (ensures that /marc/* comes after) 109 | data['@graph'] = sorted(data['@graph'], key=lambda node: 110 | (not node.get('@id', '').startswith(vocab_base), node.get('@id'))) 111 | 112 | vocab_created_ms = w3c_dtz_to_ms("2013-12-31T23:00:00.000Z") 113 | 114 | vocab_ds_url = urljoin(compiler.dataset_id, 'vocab') 115 | vocab_modified_ms = last_modified_ms(compiler.current_ds_resources) 116 | compiler._create_dataset_description(vocab_ds_url, vocab_created_ms, vocab_modified_ms) 117 | 118 | _insert_record(data['@graph'], vocab_created_ms, vocab_ds_url) 119 | vocab_node = data['@graph'][1] 120 | version = _get_repo_version() 121 | if version: 122 | vocab_node['version'] = version 123 | 124 | display = compiler.load_json('source/vocab/display.jsonld') 125 | _insert_record(display['@graph'], vocab_created_ms, vocab_ds_url) 126 | 127 | compiler.write(data, "vocab") 128 | compiler.write(display, 'vocab/display') 129 | 130 | 131 | @compiler.handler 132 | def apps(): 133 | apps = compiler.load_json('source/apps.jsonld') 134 | created_ms = w3c_dtz_to_ms("2022-11-18T15:19:35.001Z") 135 | ds_url = urljoin(compiler.dataset_id, 'sys/apps') 136 | for app in apps['@graph']: 137 | slug = app['@id'] 138 | descriptions = [app] 139 | _insert_record(descriptions, created_ms, ds_url) 140 | compiler.write({'@graph': descriptions}, slug) 141 | 142 | 143 | def _insert_record(graph, created_ms, dataset_id): 144 | entity = graph[0] 145 | record = {'@type': 'SystemRecord'} 146 | record[compiler.record_thing_link] = {'@id': entity['@id']} 147 | graph.insert(0, record) 148 | record['@id'] = compiler.generate_record_id(created_ms, entity['@id']) 149 | record['inDataset'] = [{'@id': compiler.dataset_id}, {'@id': dataset_id}] 150 | 151 | 152 | def _write_context_record(compiler, filepath, uripath, ds_url, alias=None): 153 | ctx_data = compiler.load_json(filepath) 154 | ctx_created_ms = w3c_dtz_to_ms(ctx_data.pop('created')) 155 | 156 | ctx_data['@graph'] = [{"@id": uripath, "@type": "jsonld:Context"}] 157 | _insert_record(ctx_data['@graph'], ctx_created_ms, ds_url) 158 | if alias: 159 | ctx_data['@graph'][0]['sameAs'] = [{'@id': alias}] 160 | 161 | assert uripath.startswith(ID_BASE) 162 | compiler.write(ctx_data, uripath.replace(ID_BASE, '')) 163 | 164 | 165 | if __name__ == '__main__': 166 | compiler.main() 167 | --------------------------------------------------------------------------------