├── .gitignore ├── .idea ├── encodings.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── CHANGELOG.md ├── DEVELOPER.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── app.py ├── config.py ├── controller └── routes.py ├── docker-compose.yml ├── graph_management.py ├── helper.py ├── local_vocabs ├── cover-methods.ttl ├── disturbance-assessment.ttl └── skos.ttl ├── requirements.txt ├── skos ├── __init__.py ├── collection.py ├── common_properties.py ├── concept.py ├── concept_scheme.py ├── method.py ├── register.py └── schema_org.py ├── static ├── clipboard.js ├── clippy.svg └── vocview.css ├── tasks.py ├── templates ├── alternates.html ├── base.html ├── concept-scheme-change-note.html ├── elements │ ├── alt_label.html │ ├── date.html │ └── uri.html ├── index.html ├── info.html ├── macros │ ├── alt_labels.html │ ├── bibliographic_citation.html │ ├── broaders.html │ ├── change-note.html │ ├── close_match.html │ ├── concept_hierarchy.html │ ├── date-metadata.html │ ├── definition.html │ ├── description.html │ ├── exact_match.html │ ├── header.html │ ├── in_scheme.html │ ├── label.html │ ├── mapping_statement.html │ ├── members.html │ ├── narrowers.html │ ├── popover.html │ ├── properties.html │ ├── rdfs_is_defined_by.html │ ├── render.html │ ├── schemaorg_contact_point.html │ ├── schemaorg_family_name.html │ ├── schemaorg_given_name.html │ ├── schemaorg_honorific_prefix.html │ ├── schemaorg_job_title.html │ ├── schemaorg_member_of.html │ ├── schemaorg_members.html │ ├── schemaorg_parent_org.html │ ├── schemaorg_sub_orgs.html │ └── top_concept_of.html ├── method.html ├── register.html └── skos.html ├── triplestore.py ├── vocabs.yaml └── worker.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | data.ttl 3 | local_vocabs 4 | 5 | # Created by https://www.gitignore.io/api/venv,flask,python,pycharm+all 6 | # Edit at https://www.gitignore.io/?templates=venv,flask,python,pycharm+all 7 | 8 | ### Flask ### 9 | instance/* 10 | !instance/.gitignore 11 | .webassets-cache 12 | 13 | ### Flask.Python Stack ### 14 | # Byte-compiled / optimized / DLL files 15 | __pycache__/ 16 | *.py[cod] 17 | *$py.class 18 | 19 | # C extensions 20 | *.so 21 | 22 | # Distribution / packaging 23 | .Python 24 | build/ 25 | develop-eggs/ 26 | dist/ 27 | downloads/ 28 | eggs/ 29 | .eggs/ 30 | lib/ 31 | lib64/ 32 | parts/ 33 | sdist/ 34 | var/ 35 | wheels/ 36 | pip-wheel-metadata/ 37 | share/python-wheels/ 38 | *.egg-info/ 39 | .installed.cfg 40 | *.egg 41 | MANIFEST 42 | 43 | # PyInstaller 44 | # Usually these files are written by a python script from a template 45 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 46 | *.manifest 47 | *.spec 48 | 49 | # Installer logs 50 | pip-log.txt 51 | pip-delete-this-directory.txt 52 | 53 | # Unit test / coverage reports 54 | htmlcov/ 55 | .tox/ 56 | .nox/ 57 | .coverage 58 | .coverage.* 59 | .cache 60 | nosetests.xml 61 | coverage.xml 62 | *.cover 63 | .hypothesis/ 64 | .pytest_cache/ 65 | 66 | # Translations 67 | *.mo 68 | *.pot 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # pyenv 80 | .python-version 81 | 82 | # pipenv 83 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 84 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 85 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 86 | # install all needed dependencies. 87 | #Pipfile.lock 88 | 89 | # celery beat schedule file 90 | celerybeat-schedule 91 | 92 | # SageMath parsed files 93 | *.sage.py 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # Mr Developer 103 | .mr.developer.cfg 104 | .project 105 | .pydevproject 106 | 107 | # mkdocs documentation 108 | /site 109 | 110 | # mypy 111 | .mypy_cache/ 112 | .dmypy.json 113 | dmypy.json 114 | 115 | # Pyre type checker 116 | .pyre/ 117 | 118 | ### PyCharm+all ### 119 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 120 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 121 | 122 | # User-specific stuff 123 | .idea/**/workspace.xml 124 | .idea/**/tasks.xml 125 | .idea/**/usage.statistics.xml 126 | .idea/**/dictionaries 127 | .idea/**/shelf 128 | 129 | # Generated files 130 | .idea/**/contentModel.xml 131 | 132 | # Sensitive or high-churn files 133 | .idea/**/dataSources/ 134 | .idea/**/dataSources.ids 135 | .idea/**/dataSources.local.xml 136 | .idea/**/sqlDataSources.xml 137 | .idea/**/dynamic.xml 138 | .idea/**/uiDesigner.xml 139 | .idea/**/dbnavigator.xml 140 | 141 | # Gradle 142 | .idea/**/gradle.xml 143 | .idea/**/libraries 144 | 145 | # Gradle and Maven with auto-import 146 | # When using Gradle or Maven with auto-import, you should exclude module files, 147 | # since they will be recreated, and may cause churn. Uncomment if using 148 | # auto-import. 149 | # .idea/modules.xml 150 | # .idea/*.iml 151 | # .idea/modules 152 | # *.iml 153 | # *.ipr 154 | 155 | # CMake 156 | cmake-build-*/ 157 | 158 | # Mongo Explorer plugin 159 | .idea/**/mongoSettings.xml 160 | 161 | # File-based project format 162 | *.iws 163 | 164 | # IntelliJ 165 | out/ 166 | 167 | # mpeltonen/sbt-idea plugin 168 | .idea_modules/ 169 | 170 | # JIRA plugin 171 | atlassian-ide-plugin.xml 172 | 173 | # Cursive Clojure plugin 174 | .idea/replstate.xml 175 | 176 | # Crashlytics plugin (for Android Studio and IntelliJ) 177 | com_crashlytics_export_strings.xml 178 | crashlytics.properties 179 | crashlytics-build.properties 180 | fabric.properties 181 | 182 | # Editor-based Rest Client 183 | .idea/httpRequests 184 | 185 | # Android studio 3.1+ serialized cache file 186 | .idea/caches/build_file_checksums.ser 187 | 188 | ### PyCharm+all Patch ### 189 | # Ignores the whole .idea folder and all .iml files 190 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 191 | 192 | .idea/ 193 | 194 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 195 | 196 | *.iml 197 | modules.xml 198 | .idea/misc.xml 199 | *.ipr 200 | 201 | # Sonarlint plugin 202 | .idea/sonarlint 203 | 204 | ### Python ### 205 | # Byte-compiled / optimized / DLL files 206 | 207 | # C extensions 208 | 209 | # Distribution / packaging 210 | 211 | # PyInstaller 212 | # Usually these files are written by a python script from a template 213 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 214 | 215 | # Installer logs 216 | 217 | # Unit test / coverage reports 218 | 219 | # Translations 220 | 221 | # Scrapy stuff: 222 | 223 | # Sphinx documentation 224 | 225 | # PyBuilder 226 | 227 | # pyenv 228 | 229 | # pipenv 230 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 231 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 232 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 233 | # install all needed dependencies. 234 | 235 | # celery beat schedule file 236 | 237 | # SageMath parsed files 238 | 239 | # Spyder project settings 240 | 241 | # Rope project settings 242 | 243 | # Mr Developer 244 | 245 | # mkdocs documentation 246 | 247 | # mypy 248 | 249 | # Pyre type checker 250 | 251 | ### venv ### 252 | # Virtualenv 253 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 254 | pyvenv.cfg 255 | .env 256 | .venv 257 | env/ 258 | venv/ 259 | ENV/ 260 | env.bak/ 261 | venv.bak/ 262 | pip-selfcheck.json 263 | 264 | # End of https://www.gitignore.io/api/venv,flask,python,pycharm+all -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | 8 | ## [1.2.3] - 2021-07-05 9 | ### Added 10 | - Error handling when reading data from file. 11 | 12 | 13 | ## [1.2.2] - 2021-06-23 14 | ### Added 15 | - Perform OWL RL reasoning if enabled in background task. 16 | 17 | 18 | ## [1.2.1-postfix1] - 2021-06-23 19 | ### Fixed 20 | - Create missing folders for Celery filesystem broker on web app startup. 21 | 22 | 23 | ## [1.2.1] - 2021-06-23 24 | ### Added 25 | - Trigger background task on before_first_request. 26 | 27 | 28 | ## [1.2.0] - 2021-06-22 29 | ### Added 30 | - Background tasks with Celery + filesystem broker. 31 | - Watchdog to reload data in memory when background task pulls down new data. 32 | ### Changed 33 | - RDFLib 4.2.2 -> 5.0.0 34 | 35 | 36 | ## [1.1.5] - 2021-05-21 37 | ### Added 38 | - MathJax JavaScript dependency to render Math symbols in LateX. 39 | 40 | 41 | ## [1.1.4] - 2021-05-18 42 | ### Fixed 43 | - Conditionally renders the Method's equipments list correctly now for both HTML values as well as for individuals. 44 | 45 | 46 | ## [1.1.3] - 2021-05-18 47 | ### Fixed 48 | - Rendering of the Method's equipments list. It now renders correctly a list of individuals. 49 | 50 | 51 | ## [1.1.2] - 2021-05-18 52 | ### Added 53 | - Methods template now renders additional properties. 54 | - Accessing the viewer still works when viewer re-pulls new triples from sources. 55 | 56 | 57 | ## [1.1.1] - 2021-05-11 58 | ### Added 59 | - CORS support. 60 | 61 | 62 | ## [1.1.0] - 2020-02-22 63 | ### Added 64 | - Re-pull triples for 'memory' store mode. 65 | ### Changed 66 | - Improved the views for different SKOS types (Concept, ConceptScheme, Collection). No requirement for use with OrderedCollection, so will be ignored for now. This improvement will allow us to manage and add different views more cleanly in the future. 67 | 68 | 69 | ## [1.0.6] - 2020-11-09 70 | ### Added 71 | - python-dotenv 72 | - Load from .env file for config.py 73 | - Viewer now understands owl:deprecated and does not show it to the user through SKOS properties narrower, broader, hasTopConcept and in the search. 74 | 75 | 76 | ## [1.0.5] - 2020-11-03 77 | ### Fixed 78 | - Time since last pull of remote data showing 'None' in the jinja template. Now it shows the time since last pull as expected (regardless of triplestore config type). 79 | 80 | 81 | ## [1.0.4] - 2020-11-03 82 | ### Changed 83 | - Triplestore type in config.py changed to 'memory' as default. Better for deployments by removing the overhead of loading from disk a pickle file on each request. 84 | - Only call the get_db function in @app.before_request if the RDF triples are not yet loaded in memory. 85 | - get_properties function calls get_label function with the parameter 'create' as False. Significantly improving the speed of loading concepts since properties don't need to make a HTTP call the dereference the URI of properties without labels loaded in memory. 86 | 87 | 88 | ## [1.0.3] - 2020-10-23 89 | ### Added 90 | - Functionality to show and redirect externally linked concepts. 91 | 92 | 93 | ## [1.0.2] - 2020-07-30 94 | ### Added 95 | - Methods now show hasParameter and hasCategoricalVariableCollection properties in the html view. 96 | 97 | 98 | ## [1.0.1] - 2020-07-28 99 | ### Added 100 | - 404 status code for URIs not found or does not have a recognised class type. 101 | - New Method view. See http://linked.data.gov.au/def/ausplots-cv/03ba5e75-f322-4f80-a1e3-5a845e4dd807 as an example. 102 | - Started moving macros that do not need to be macros into `templates/elements`. These can be used with the Jinja2 `{% includes %}` statement and no longer need to be imported (like with macros). 103 | - VocView can now dynamically read and present the version number from the CHANGELOG.md file. 104 | ### Removed 105 | - The default alternates view options as 'skos' for the header bar in the HTML view. 106 | ### Changed 107 | - Improved some of the macros in Jinja2. Some don't need to be macros and have since been changed to a normal Jinja2 template. 108 | 109 | 110 | ## [1.0.0] - 2020-04-17 111 | ### Added 112 | - "Version 1.0.0 release" -------------------------------------------------------------------------------- /DEVELOPER.md: -------------------------------------------------------------------------------- 1 | # Developer's Guide 2 | 3 | ## Extending the properties displayed for SKOS things 4 | By default, all properties which are not on the "ignored list of properties" are displayed with their property name, URI, and the property value. VocView has the capability of extending on this to display certain properties in a custom way. For example, the list of broader and narrower concepts are displayed differently where each concept is displayed with its readable label with a clickable link. This link resolves within the VocView system to display the information related to the concept. 5 | 6 | To make this possible, a few steps were taken: 7 | - First, add the property to the ignore list in the function `get_properties()` in [skos/__init__.py](skos/__init__.py). 8 | - The ignored property should be `SKOS.broader`. 9 | - In the `Concept` class in [skos/concept.py](skos/concept.py), add an attribute to the class as `self.broaders = []`, which will be a list (since a concept can have zero to many broader relationships). 10 | - In [skos/__init__.py](skos/__init__.py), write a function which will retrieve all the broader concepts for the given concept. The function signature should take in one argument `uri`, which will be the URI of the *focus* concept. Append the results to a list and return it. 11 | - Back in the `Concept` class in [concept.py](skos/concept.py), assign `self.broaders = skos.get_broaders(uri)`. 12 | - Now create a html file in the directory [templates/macros](templates/macros) called `broaders.html`. Write a Jinja2 macro on how you want the broaders to be displayed for a concept. 13 | - In [templates/skos.html](templates/skos.html), add the import statement for the new macro and render it here. -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3-alpine 2 | 3 | RUN apk add --no-cache git bash vim 4 | 5 | WORKDIR /app 6 | 7 | RUN pip3 install --no-cache-dir --upgrade pip \ 8 | && pip3 install --no-cache-dir --upgrade setuptools 9 | 10 | COPY controller /app/controller 11 | COPY skos /app/skos 12 | COPY static /app/static 13 | COPY templates /app/templates 14 | 15 | COPY app.py /app 16 | COPY config.py /app 17 | COPY helper.py /app 18 | COPY requirements.txt /app 19 | COPY triplestore.py /app 20 | COPY vocabs.yaml /app 21 | COPY graph_management.py /app/graph_management.py 22 | COPY tasks.py /app/tasks.py 23 | COPY worker.py /app/worker.py 24 | 25 | COPY CHANGELOG.md /app 26 | 27 | RUN pip install --no-cache-dir -r requirements.txt 28 | RUN pip install --no-cache-dir gunicorn 29 | 30 | RUN mkdir /app/data 31 | RUN mkdir /app/broker 32 | RUN chown -R 1000:1000 /app 33 | USER 1000 34 | 35 | #CMD gunicorn --workers=1 --threads=2 --forwarded-allow-ips=* --bind=0.0.0.0:8000 --limit-request-line=8190 --log-level=info app:application -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Edmond Chuc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | version = 1.2.3 2 | 3 | build: 4 | docker build -t ternau/vocview:$(version) . 5 | docker build -t ternau/vocview:latest . 6 | 7 | run: 8 | docker run --rm --name vocview -p 8000:8000 ternau/vocview:$(version) 9 | 10 | push: 11 | docker push ternau/vocview:$(version) 12 | docker push ternau/vocview:latest -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VocView 2 | [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) 3 | [![GitHub license](https://img.shields.io/github/license/Naereen/StrapDown.js.svg)](https://opensource.org/licenses/MIT) 4 | [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/edmondchuc/vocview/graphs/commit-activity) 5 | [![Website](https://img.shields.io/website/https/www.edmondchuc.com.svg?down_color=red&down_message=Edmond%20Chuc&label=Author&style=social&up_color=blue&up_message=Edmond%20Chuc)](https://www.edmondchuc.com) 6 | 7 | *A Python web application to serve SKOS-encoded vocabularies as Linked Data.* 8 | 9 | ### VocView instances 10 | Click on a badge to see a live instance of VocView. 11 | 12 | [![corveg](https://img.shields.io/website/http/linkeddata.tern.org.au/viewer/corveg.svg?down_color=red&down_message=offline&label=CORVEG&up_color=green&up_message=online)](http://linkeddata.tern.org.au/viewer/corveg) 13 | 14 | [![tern](https://img.shields.io/website/http/linkeddata.tern.org.au/viewer/tern.svg?down_color=red&down_message=offline&label=TERN&up_color=green&up_message=online)](http://linkeddata.tern.org.au/viewer/tern) 15 | 16 | [![ausplots](https://img.shields.io/website/http/linkeddata.tern.org.au/viewer/ausplots.svg?down_color=red&down_message=offline&label=AUSPLOTS&up_color=green&up_message=online)](http://linkeddata.tern.org.au/viewer/ausplots) 17 | 18 | 19 | ## SKOS 20 | SKOS stands for [Simple Knowledge Organization System](https://www.w3.org/2004/02/skos/), a W3C recommendation, as of April, 2019. 21 | 22 | SKOS is used to create controlled vocabularies and taxonomies using the [RDF](https://en.wikipedia.org/wiki/Resource_Description_Framework) model. Combined with models created in the [Web Ontology Language](https://en.wikipedia.org/wiki/Web_Ontology_Language) (OWL), SKOS allows [Semantic Web](https://en.wikipedia.org/wiki/Semantic_Web) users to provide information in an unambiguous, interoperable, and reusable manner. 23 | 24 | 25 | ## Linked Data 26 | VocView uses the [pyLDAPI](https://pyldapi.readthedocs.io/en/latest/) library to provide registry information as Linked Data. The library also provides VocView with different *views* of things. For example, a register view to describe a register and its items, an alternates view, to describe the alternative views of the same resource, and a SKOS view, to describe SKOS concept schemes and concepts. 27 | 28 | VocView also provides different *formats* for things, such as text/html, text/turtle, application/rdf+xml, etc. 29 | 30 | Specifying views and formats can be done by using the query string arguments `_view` and `_format` or content negotiation with an *Accept* header. 31 | 32 | ##### Example usage 33 | ```bash 34 | http://localhost:5000/vocabulary/?_view=alternates&format=html 35 | ``` 36 | The above request will show the HTML page of the alternates view, and display all the available views and formats in a table. 37 | 38 | All vocabulary data are accessible via the Linked Data API. 39 | 40 | 41 | ## Getting started 42 | 43 | ### Installation 44 | Clone this repository's master branch 45 | ```bash 46 | git clone https://github.com/edmondchuc/vocview.git 47 | ``` 48 | 49 | Change directory into vocview and install the Python dependencies 50 | ```bash 51 | pip install -r requirements.txt 52 | ``` 53 | 54 | Note: Python *virtualenv* is highly recommended. See [this](https://docs.python-guide.org/dev/virtualenvs/) article on Python virtual environments. 55 | 56 | ### Configuration 57 | Add some vocabulary sources in the `vocabs.yaml` file 58 | ```yaml 59 | download: 60 | material_types: 61 | source: https://vocabs.ands.org.au/registry/api/resource/downloads/524/ga_material-type_v1-0.ttl 62 | format: turtle 63 | local: 64 | skos: 65 | source: skos.ttl 66 | format: turtle 67 | rva: 68 | resource_endpoint: 'https://demo.ands.org.au/vocabs-registry/api/resource/vocabularies/{}?includeVersions=true&includeAccessPoints=true&includeRelatedEntitiesAndVocabularies=false' 69 | download_endpoint: 'https://demo.ands.org.au/vocabs-registry/api/resource/downloads/{}' 70 | extension: ttl # Use Turtle as it has excellent compression compared to other RDF serialisations 71 | format: turtle 72 | ids: [ 73 | 245, # CORVEG 74 | ] 75 | ``` 76 | The example snippet above shows how to enable three types of vocabulary files, an online file, a local file, and a file accessed via [Research Vocabularies Australia](https://vocabs.ands.org.au/)'s (RVA) API. 77 | 78 | The `material_types` is an online file (hence the `download` node), where the `source` node lists the absolute URL of the file. The `format` node tells the RDFLib parser what format the file is in. See the list of available parsers for RDFLib [here](https://rdflib.readthedocs.io/en/stable/plugin_parsers.html). 79 | 80 | The `local` node lists RDF files on the local filesystem. By default, the path of the `source` node is relative to the `local_vocabs` directory in this repository. 81 | 82 | The RVA resource finds the latest API endpoint for a given project referenced by the ID `245`. The extension informs the API what format we want to download and the format informs the VocView system what file type to expect. The `resource_endpoint` is used to determine the RVA project's latest version's *download ID*. The *download ID* is then used with the `download_endpoint` to download the latest RDF resource. 83 | 84 | > Note: there is significance with loading in the `skos.ttl` file, which is a modified version of the SKOS definition. The modifications consist of removing a few `rdfs:subPropertyOf`statements used by the rule-based inference engine (discussed later). Loading this file in to the graph allows the inferencer to create new triples. 85 | 86 | 87 | ## Rule-based inferencing 88 | ### OWLRL 89 | VocView utilises the Python rule-based inferencer for RDF known as [owlrl](https://owl-rl.readthedocs.io/en/latest/). The inferencer is used in VocView to expand the graph on SKOS-specific properties. To expand the graph on SKOS properties, ensure that the `skos.ttl` is declared in `vocabs.yaml`. Additional ontologies can also be loaded in to expand the graph further. 90 | 91 | A good example of why an inferencing engine is used in VocView is to expand properties that have inverse properties of itself. For example, declaring top concepts with `skos:topConceptOf` need only be declared within concepts of a concept scheme. The inferencing engine is capable of performing a deductive closure and add the inverse statements of `skos:topConceptOf` to the concept scheme as `skos:hasTopConcept`. The HTML view of the concept scheme will now display a listing of the top concepts. Without the additional triples added by the inferencing engine, the listing will not be displayed (as the information is missing). 92 | 93 | The downside of using a rule-based inferencer like owlrl is the expensive calculations. This causes a slow start-up time for VocView. 94 | 95 | It is recommended to have inferencing performed on the source repository before loading the data into VocView. 96 | 97 | Therefore, inferencing is an optional feature. 98 | 99 | 100 | ### Skosify 101 | An alternative to rule-based inferencing is the Python [skosify](https://skosify.readthedocs.io/en/latest/index.html) library. This library contains a collection of inferencing functions specifically for SKOS properties. Since this library only focuses on SKOS things, it may be much faster than the owlrl library, thus reducing start-up time. 102 | 103 | *This is a potential feature for a future VocView version*. 104 | 105 | 106 | ## Search within registers 107 | VocView contains registers for vocabularies and concepts. 108 | 109 | ### Naive search 110 | The current search implementation is a naive, string-based text-match on the register items' labels. For simple searches on the label, this works well (and fast) enough. Time complexity is Θ(n) where *n* is the number of items in the register. 111 | 112 | This naive search matches not only full-text in labels, but also partial text. It is also case-insensitive. 113 | 114 | E.g. a search query "*form*" will match: 115 | - *Landform type concepts* . 116 | - *Structural formation classification system concepts* 117 | 118 | ### Whoosh (full text search) 119 | *To be implemented in VocView...* 120 | 121 | Whoosh is a pure Python library text indexer for full text search. It is suitable for light-weight Python applications like VocView, to provide full text search, without requiring external dependencies (outside of Python). 122 | 123 | A description of Whoosh from the official [documentation](https://whoosh.readthedocs.io/en/latest/index.html): 124 | 125 | - *Like one of its ancestors, Lucene, Whoosh is not really a search engine, it’s a programmer library for creating a search engine.* 126 | 127 | Creating a full text search engine using Whoosh would greatly improve the capabilities of searching items within registers. Whoosh would allow users to search for not only key words to match register items' labels, but also their *definition*, *date*, or any other interesting properties. 128 | 129 | Whoosh has been used in related projects already, and it will only take *probably* a full weekend to implement. 130 | 131 | 132 | ## Persistent store 133 | On start-up, the first request performs the loading of all the RDF files into an in-memory graph. It then performs a deductive closure to expand the graph with additional triples outlined in the `skos.ttl`. This process makes the initial start-up time very slow. 134 | 135 | One way to solve this is to have persistence of the graph between server restarts. 136 | 137 | There are three options to choose from in `config.py`'s `Config` class. 138 | 139 | - memory 140 | - pickle 141 | - sleepycat 142 | - sqlite (not implemented) 143 | 144 | > Note: to re-index, simply delete the `triplestore.p` file if using the **pickle** method or delete the `triplestore` directory if using the **sleepycat** method. 145 | 146 | ### Memory 147 | There is no *persistence* when using `memory` as this mode requires loading all the RDF files into the graph on start-up each time. 148 | 149 | #### Pros 150 | Start-up time is very slow but performance is fast as the queries are performed on the graph, which is in memory (RAM). 151 | 152 | #### Cons 153 | Memory use is high as it requires the whole graph to be stored in the application's memory. 154 | 155 | ### Pickle 156 | VocView supports saving the graph object to disk as a binary file. In Python, this is known as [pickling](https://docs.python.org/3/library/pickle.html). On start-up, VocView loads in the graph and saves it to disk. Each subsequent restart, VocView will automatically load in the pickled graph object from disk if it exists. 157 | 158 | #### Pros 159 | Performance is very fast compared to other persistent store methods. Queries made by each web request are performed on the in-memory graph, which is very fast. 160 | 161 | #### Cons 162 | Memory use is high as it requires the whole graph to be stored in the application's memory. 163 | 164 | ### Sleepycat 165 | (The defunct) Sleepycat was the company that maintained the freely-licensed Berkeley DB, a data store written in C for embedded systems. 166 | 167 | RDFLib currently ships Sleepycat by default. See RDFLib's documentation on persistence [here](https://rdflib.readthedocs.io/en/stable/persistence.html). 168 | 169 | #### Pros 170 | Extremely low memory usage compared to a method using in-memory graph. Good for instances of VocView with a large collection of vocabularies. 171 | 172 | #### Cons 173 | Roughly 10-20% slower than in-memory graph (due to filesystem read/write speeds). Requires installing Berkeley DB to the host system as well as downloading the Python **bsddb3** package source and installing it manually. 174 | 175 | #### Installing Sleepycat (Berkeley DB) 176 | ##### Ubuntu 18.04 and above 177 | Install the Berkeley DB 178 | ```bash 179 | sudo apt install python3-bsddb3 180 | ``` 181 | Install the Python package 182 | ```bash 183 | pip install bsddb3 184 | ``` 185 | You now can use the Sleepycat as a persistent store. 186 | 187 | ##### macOS Mojave and above 188 | First, ensure that [brew](https://brew.sh/) is installed on macOS, then run 189 | ```bash 190 | brew install berkeley-db 191 | ``` 192 | Download the **bsddb3** source from PyPI like https://pypi.python.org/packages/source/b/bsddb3/bsddb3-5.3.0.tar.gz#md5=d5aa4f293c4ea755e84383537f74be82 193 | 194 | Once unzipped and inside the package, run 195 | ```bash 196 | python setup.py install --berkeley-db=$(brew --prefix)/berkeley-db/5.3.21/ 197 | ``` 198 | You now can use the Sleepycat as a persistent store. 199 | 200 | ### SQLite (not implemented) 201 | According to the textbook *Programming the Semantic Web* [1], it is possible to use [SQLite](https://www.sqlite.org/index.html) as the persistent data store for an RDFLib graph. 202 | 203 | It will be a good experiment to investigate on the ease of using this, since most systems come with SQLite pre-installed (unlike Sleepycat's Berkeley DB). 204 | 205 | It will also be interesting to see the speed differences between SQLite and Sleepycat's store. 206 | 207 | 208 | ## References 209 | [1] Segaran, Evans, & Taylor. (2009). Programming the Semantic Web (1st ed.). Beijing ; Sebastopol, CA: O'Reilly. 210 | 211 | 212 | ## License 213 | See [LICENSE](LICENSE). 214 | 215 | ## Developer's guide 216 | See [DEVELOPER.md](DEVELOPER.md). 217 | 218 | 219 | ## Contact 220 | **Edmond Chuc** 221 | [e.chuc@uq.edu.au](mailto:e.chuc@uq.edu.au) 222 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import atexit 2 | import logging 3 | 4 | from flask import Flask, request 5 | from flask_cors import CORS 6 | from werkzeug.middleware.dispatcher import DispatcherMiddleware 7 | from werkzeug.serving import run_simple 8 | from watchdog.observers import Observer 9 | 10 | from config import Config 11 | from controller.routes import routes 12 | import helper 13 | from graph_management import get_graph, VocviewFileSystemEventHandler 14 | 15 | logger = logging.getLogger(__name__) 16 | logging.basicConfig(level=logging.INFO) 17 | 18 | app = Flask(__name__) 19 | cors = CORS(app) 20 | 21 | app.register_blueprint(routes) 22 | 23 | application = DispatcherMiddleware( 24 | None, { 25 | Config.SUB_URL: app 26 | } 27 | ) 28 | 29 | 30 | # Set up python-watchdog 31 | path = 'data' 32 | observer = Observer() 33 | observer.schedule(VocviewFileSystemEventHandler(), path) 34 | observer.start() 35 | 36 | 37 | @app.before_request 38 | def before(): 39 | # Config.g = Triplestore.get_db(Config.triplestore_type) 40 | Config.g = get_graph(Config) 41 | 42 | 43 | @app.after_request 44 | def after(response): 45 | return response 46 | 47 | 48 | @app.before_first_request 49 | def init(): 50 | # Set the URL root of this web application 51 | Config.url_root = request.url_root 52 | logging.info('Loaded config:') 53 | logging.info(Config.__dict__) 54 | 55 | logging.info('Triggering background task from vocview app.') 56 | import worker # Import to create directories if missing. 57 | from tasks import fetch_data 58 | fetch_data.s().apply_async() 59 | 60 | 61 | @app.context_processor 62 | def context_processor(): 63 | return dict(h=helper, config=Config) 64 | 65 | 66 | @atexit.register 67 | def shutdown(): 68 | logger.info('Performing cleanup') 69 | observer.stop() 70 | 71 | 72 | if __name__ == '__main__': 73 | # Run this only for development. Production version should use a dedicated WSGI server. 74 | if Config.SUB_URL: 75 | print('Starting simple server') 76 | run_simple('0.0.0.0', port=5000, application=application, use_reloader=True) 77 | else: 78 | print('Starting Flask server') 79 | app.run(host='0.0.0.0', port='5000', debug=True) 80 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | from dotenv import load_dotenv 5 | from rdflib import Graph 6 | 7 | load_dotenv() 8 | 9 | 10 | def get_version(): 11 | with open('CHANGELOG.md', 'r') as f: 12 | while True: 13 | line = f.readline() 14 | if re.search('## \[[0-9].[0-9].[0-9]\]', line): 15 | return line.split('[')[1].split(']')[0] 16 | 17 | 18 | class Config: 19 | title = os.environ.get('VOCVIEW_TITLE', 'VocView') 20 | 21 | # Used in the DCAT metadata. 22 | description = os.environ.get('VOCVIEW_DESCRIPTION', 'SKOS controlled vocabulary viewer') 23 | 24 | # URL root of this web application. This gets set in the before_first_request function. 25 | url_root = None # No need to set. 26 | 27 | # Subdirectory of base URL. Example, the '/corveg' part of 'vocabs.tern.org.au/corveg' 28 | SUB_URL = os.environ.get('VOCVIEW_SUB_URL', '') 29 | 30 | # Path of the application's directory. 31 | APP_DIR = os.path.dirname(os.path.realpath(__file__)) 32 | 33 | # Vocabulary sources config. file. 34 | VOCAB_SOURCES = 'vocabs.yaml' 35 | 36 | # Rule-based reasoner 37 | reasoner = True 38 | 39 | FLASK_ENV = os.environ.get('FLASK_ENV', 'production') 40 | 41 | # -- Triplestore --------------------------------------------------------------------------------------------------- 42 | # 43 | # Options: 44 | # 45 | # - memory 46 | # - No persistence, load in triples on instance start-up (slow start-up time). Graph is required to be kept in 47 | # memory during application's lifetime. Not recommended due to slow start-up. 48 | # - Difficulty: easy 49 | # 50 | # - pickle 51 | # - Persistent store by saving a binary (pickle) copy of the Python rdflib.Graph object to disk. Graph is 52 | # required to be in memory during application's lifetime. Fast start-up time and fast performance, uses 53 | # significantly more memory than Sleepycat. Exact same as the memory method except it persists between 54 | # application restarts. 55 | # - Difficulty: easy 56 | # 57 | # - sleepycat 58 | # - Persistent store by storing the triples in the now defunct Sleepycat's Berkeley DB store. Requires external 59 | # libraries to be installed on the system before using. Does not require to have the whole triplestore in 60 | # memory. Performance is slightly slower than the pickle method (maybe around 10-20%) but uses much less memory. 61 | # For each request, only the required triples are loaded into the application's memory. 62 | # - Difficulty: intermediate 63 | triplestore_type = 'memory' if FLASK_ENV == 'production' else 'pickle' 64 | 65 | # The time which the store is valid before re-harvesting in the background. 66 | # store_hours = int(os.environ.get('VOCVIEW_STORE_HOURS', '0')) 67 | # store_minutes = int(os.environ.get('VOCVIEW_STORE_MINUTES', '60')) 68 | store_seconds = int(os.environ.get('VOCVIEW_STORE_SECONDS', '3600')) 69 | 70 | # Triplestore disk path 71 | _triplestore_name_pickle = 'triplestore.p' 72 | triplestore_path_pickle = os.path.join(APP_DIR, _triplestore_name_pickle) 73 | _triplestore_name_sleepy_cat = 'triplestore' 74 | triplestore_path_sleepy_cat = os.path.join(APP_DIR, _triplestore_name_sleepy_cat) 75 | 76 | _version = get_version() 77 | 78 | g: Graph 79 | -------------------------------------------------------------------------------- /controller/routes.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, render_template, request, Response, redirect 2 | from munch import munchify 3 | from pyldapi import Renderer 4 | 5 | from config import Config 6 | import skos 7 | 8 | routes = Blueprint('routes', __name__) 9 | 10 | 11 | def match(vocabs, query): 12 | """ 13 | Generate a generator of vocabulary items that match the search query 14 | :param vocabs: The vocabulary list of items. 15 | :param query: The search query string. 16 | :return: A generator of words that match the search query. 17 | :rtype: generator 18 | """ 19 | for word in vocabs: 20 | if query.lower() in word[1].lower(): 21 | yield word 22 | 23 | 24 | def process_search(query, items): 25 | results = [] 26 | 27 | if query: 28 | for m in match(items, query): 29 | results.append(m) 30 | results.sort(key=lambda v: v[1]) 31 | 32 | return results 33 | 34 | return items 35 | 36 | 37 | @routes.route('/download', methods=['GET']) 38 | def download(): 39 | format = request.args.get('format') 40 | 41 | if format is None: 42 | format = 'turtle' 43 | 44 | # Check if format is one of the supported formats 45 | if format not in Renderer.RDF_SERIALIZER_MAP.values(): 46 | return '

Invalid download format type

\nPlease set the format type to be one of the following values: {}'\ 47 | .format(set(Renderer.RDF_SERIALIZER_MAP.values())) 48 | 49 | mimetype=None 50 | for key, val in Renderer.RDF_SERIALIZER_MAP.items(): 51 | if val == format: 52 | mimetype = key 53 | 54 | file_extension = {'turtle': '.ttl', 'n3': '.n3', 'json-ld': '.jsonld', 'nt': '.nt', 'xml': '.rdf'}.get(format) 55 | 56 | rdf_content = Config.g.serialize(format=format) 57 | 58 | return Response( 59 | response=rdf_content, mimetype=mimetype, 60 | headers={'Content-Disposition': 'attachment; filename={}'.format(Config.title + file_extension)} 61 | ) 62 | 63 | 64 | @routes.route('/', methods=['GET']) 65 | def index(): 66 | return render_template('index.html') 67 | 68 | 69 | @routes.route('/vocabulary/', methods=['GET']) 70 | def render_vocabulary_register(): 71 | page = request.values.get('page') 72 | if page is None: 73 | page = 1 74 | 75 | items = skos.list_concept_schemes_and_collections() 76 | 77 | query = request.values.get('search') 78 | items = process_search(query, items) 79 | 80 | total_items_count = len(items) 81 | page_from = int(page) 82 | page_size = 20 83 | 84 | items = items[(page_from - 1) * page_size:page_size * page_from] 85 | 86 | # TODO: Check why munchify is duplicating the dct:created and dct:modified data in an element of item. 87 | # items = munchify(items) 88 | 89 | r = skos.Register(request, 'Register of SKOS vocabularies', 90 | 'This register contains a listing of SKOS vocabularies as concept schemes or collections.', 91 | items, ['http://www.w3.org/2004/02/skos/core#ConceptScheme', 'http://www.w3.org/2004/02/skos/core#Collection'], 92 | total_items_count=total_items_count, 93 | register_template='register.html', 94 | title='Vocabularies', 95 | description='Register of all vocabularies in this system.', 96 | search_query=query) 97 | return r.render() 98 | 99 | 100 | @routes.route('/concept/', methods=['GET']) 101 | def render_concept_register(): 102 | page = request.values.get('page') 103 | if page is None: 104 | page = 1 105 | 106 | items = skos.list_concepts() 107 | 108 | query = request.values.get('search') 109 | items = process_search(query, items) 110 | 111 | total_items_count = len(items) 112 | page_from = int(page) 113 | page_size = 20 114 | 115 | items = items[(page_from - 1) * page_size:page_size * page_from] 116 | 117 | # TODO: Check why munchify is duplicating the dct:created and dct:modified data in an element of item. 118 | # items = munchify(items) 119 | 120 | r = skos.Register(request, 121 | 'Register of SKOS concepts', 122 | 'This register contains a listing of all SKOS concepts within this system.', 123 | items, ['http://www.w3.org/2004/02/skos/core#Concept'], 124 | total_items_count=total_items_count, 125 | register_template='register.html', 126 | title='Concepts', 127 | description='Register of all vocabulary concepts in this system.', 128 | search_query=query) 129 | return r.render() 130 | 131 | 132 | @routes.route('/id/', methods=['GET']) 133 | def ob(uri): 134 | # TODO: Issue with Apache, Flask, and WSGI interaction where multiple slashes are dropped to 1 (19/04/2019). 135 | # E.g. The URI http://linked.data.gov.au/cv/corveg/cover-methods when received at this endpoint becomes 136 | # http:/linked.data.gov.au/cv/corveg/cover-methods. Missing slash after the HTTP protocol. This only happens 137 | # using the Flask URL variable. It worked fine with query string arguments. 138 | # . 139 | # Reference issue online which describes the exact same issue here: 140 | # - http://librelist.com/browser/flask/2012/8/24/not-able-to-pass-a-url-parameter-with-in-it/ 141 | 142 | # Ugly fix for Apache multiple slash issue. 143 | 144 | # Get the protocol (http/https) 145 | protocol = uri.split(':')[0] 146 | 147 | # Fix the URI if it is missing double slash after the HTTP protocol. 148 | if protocol == 'http': 149 | if uri[6] != '/': 150 | uri = 'http://' + uri[6:] 151 | elif protocol == 'https': 152 | if uri[7] != '/': 153 | uri = 'https://' + uri[7:] 154 | 155 | # Check if the URI has a file extension-like suffix 156 | rdf_suffix = uri.split('.')[-1] 157 | rdf_format = None 158 | file_extensions = ['rdf', 'ttl', 'xml', 'nt', 'jsonld', 'n3'] 159 | rdf_formats = ['application/rdf+xml', 'text/turtle', 'application/rdf+xml', 'application/n-triples', 'application/ld+json', 'text/n3'] 160 | for i in range(len(file_extensions)): 161 | if file_extensions[i] == rdf_suffix: 162 | rdf_format = rdf_formats[i] 163 | uri = uri.replace('.' + rdf_suffix, '') 164 | break 165 | 166 | skos_type = skos.get_uri_skos_type(uri) 167 | 168 | if skos_type == skos.METHOD: 169 | from skos.method import MethodRenderer 170 | r = MethodRenderer(uri, request) 171 | if rdf_format: 172 | r.format = rdf_format 173 | return r.render() 174 | 175 | if skos_type == skos.CONCEPTSCHEME: 176 | r = skos.ConceptSchemeRenderer(uri, request) 177 | if rdf_format: 178 | r.format = rdf_format 179 | return r.render() 180 | elif skos_type == skos.COLLECTION: 181 | r = skos.CollectionRenderer(uri, request) 182 | if rdf_format: 183 | r.format = rdf_format 184 | return r.render() 185 | elif skos_type == skos.CONCEPT: 186 | r = skos.ConceptRenderer(uri, request) 187 | if rdf_format: 188 | r.format = rdf_format 189 | return r.render() 190 | 191 | return redirect(uri, code=302) 192 | 193 | return '

404 :(

URI supplied does not exist or is not a recognised class type.

', 404 194 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | build: . 4 | command: gunicorn --workers=1 --threads=2 --forwarded-allow-ips=* --bind=0.0.0.0:5000 --limit-request-line=8190 --log-level=info app:application 5 | ports: 6 | - 5000:5000 7 | volumes: 8 | - broker:/app/broker 9 | - data:/app/data 10 | environment: 11 | - VOCVIEW_STORE_SECONDS=60 12 | 13 | worker: 14 | build: . 15 | command: ["celery", "-A", "worker.app", "worker", "--concurrency=1", "--hostname=worker@%h", "--loglevel=INFO"] 16 | volumes: 17 | - broker:/app/broker 18 | - data:/app/data 19 | environment: 20 | - CELERY_BROKER_URL=filesystem:// 21 | - CELERY_BROKER_FOLDER=/app/broker 22 | - VOCVIEW_STORE_SECONDS=60 23 | 24 | scheduler: 25 | build: . 26 | command: ["celery", "-A", "tasks.app", "beat", "--loglevel=INFO"] 27 | volumes: 28 | - broker:/app/broker 29 | - data:/app/data 30 | environment: 31 | - CELERY_BROKER_URL=filesystem:// 32 | - CELERY_BROKER_FOLDER=/app/broker 33 | - VOCVIEW_STORE_SECONDS=60 34 | 35 | volumes: 36 | broker: 37 | data: 38 | -------------------------------------------------------------------------------- /graph_management.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import time 4 | import traceback 5 | from typing import Type 6 | 7 | from rdflib import Graph 8 | from watchdog.events import FileSystemEventHandler 9 | 10 | from config import Config 11 | 12 | last_trigger_time = time.time() 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | def load_graph(set_on_config: bool = False): 17 | g = Graph() 18 | path = 'data/data.ttl' 19 | logger.info(f'Loading data from path {path}') 20 | if os.path.isfile(path): 21 | try: 22 | g.parse(path, format='turtle') 23 | logger.info(f'Loading completed.') 24 | except Exception: 25 | traceback.print_exc() 26 | # This block is only possible if load_graph() is triggered by watchdog. 27 | return None 28 | if set_on_config: 29 | Config.g = g 30 | return g 31 | 32 | 33 | def get_graph(config: Type[Config]): 34 | if not hasattr(config, 'g'): 35 | return load_graph() 36 | else: 37 | return config.g 38 | 39 | 40 | class VocviewFileSystemEventHandler(FileSystemEventHandler): 41 | def on_modified(self, event): 42 | global last_trigger_time 43 | current_time = time.time() 44 | if event.src_path.find('~') == -1 and (current_time - last_trigger_time) > 1: 45 | last_trigger_time = current_time 46 | load_graph(set_on_config=True) 47 | -------------------------------------------------------------------------------- /helper.py: -------------------------------------------------------------------------------- 1 | from markdown import markdown 2 | from flask import url_for 3 | from rdflib.namespace import DCTERMS 4 | from rdflib import BNode, URIRef 5 | from bs4 import BeautifulSoup 6 | 7 | from config import Config 8 | # from triplestore import Triplestore 9 | 10 | import re 11 | from urllib.parse import quote_plus 12 | from datetime import datetime, timedelta 13 | 14 | 15 | def render_concept_tree(html_doc): 16 | soup = BeautifulSoup(html_doc, 'html.parser') 17 | 18 | concept_hierarchy = soup.find(id='concept-hierarchy') 19 | 20 | uls = concept_hierarchy.find_all('ul') 21 | 22 | for i, ul in enumerate(uls): 23 | # Don't add HTML class nested to the first 'ul' found. 24 | if not i == 0: 25 | ul['class'] = 'nested' 26 | if ul.parent.name == 'li': 27 | temp = BeautifulSoup(str(ul.parent.a.extract()), 'html.parser') 28 | ul.parent.insert(0, BeautifulSoup('', 'html.parser')) 29 | ul.parent.span.insert(0, temp) 30 | return soup 31 | 32 | 33 | # def get_triplestore_created_time(): 34 | # """Get the string message of the last time the local graph cache was created.""" 35 | # 36 | # MSG = 'Last updated {}.' 37 | # for created_time in Config.g.objects(Triplestore.THIS_GRAPH, DCTERMS.created): 38 | # created_time = created_time.toPython() 39 | # now = datetime.now() 40 | # now -= timedelta(minutes=1) 41 | # 42 | # last_updated = (datetime.now() - created_time).seconds // 60 43 | # if not last_updated: 44 | # LAST_UPDATE_VALUE = 'just now' 45 | # else: 46 | # LAST_UPDATE_VALUE = f'{last_updated} minutes ago' 47 | # 48 | # return MSG.format(LAST_UPDATE_VALUE) 49 | 50 | 51 | def uri_label(uri): 52 | return uri.split('#')[-1].split('/')[-1] 53 | 54 | 55 | def render_property_restricted(text): 56 | if isinstance(text, str): 57 | length = 175 58 | if len(text) > length: 59 | return text[:length] + '...' 60 | return text 61 | 62 | 63 | def is_list(property): 64 | if isinstance(property, list): 65 | return True 66 | return False 67 | 68 | 69 | def render_popover(label, uri): 70 | return '{0}'.format(label, uri) 71 | 72 | 73 | def render(text): 74 | if type(text) == BNode: 75 | bnode = text 76 | text = '' 82 | return text 83 | 84 | if text[:4] == 'http': 85 | return '

{0}

'.format(text) 86 | 87 | email_pattern = r"[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*" \ 88 | r"[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?" 89 | if re.match(email_pattern, text): 90 | return '

{0}

'.format(text) 91 | 92 | return markdown(text) 93 | 94 | 95 | def url_encode(url): 96 | return quote_plus(url) 97 | 98 | 99 | def render_instance_uri(uri, label): 100 | return '

{}

'.format(url_for('routes.ob', uri=uri), label) 101 | -------------------------------------------------------------------------------- /local_vocabs/cover-methods.ttl: -------------------------------------------------------------------------------- 1 | @prefix apc: . 2 | @prefix apni: . 3 | @prefix biod: . 4 | @prefix corveg: . 5 | @prefix corveg-def: . 6 | @prefix cv-corveg: . 7 | @prefix data: . 8 | @prefix dcterms: . 9 | @prefix disturbance: . 10 | @prefix dwct: . 11 | @prefix geo: . 12 | @prefix location-corveg: . 13 | @prefix locn: . 14 | @prefix ogroup: . 15 | @prefix op: . 16 | @prefix owl: . 17 | @prefix plot: . 18 | @prefix prov: . 19 | @prefix rdf: . 20 | @prefix rdfs: . 21 | @prefix site-corveg: . 22 | @prefix site-strata-corveg: . 23 | @prefix site-tax-strata-corveg: . 24 | @prefix skos: . 25 | @prefix sosa: . 26 | @prefix ssn-ext: . 27 | @prefix unit: . 28 | @prefix xml: . 29 | @prefix xsd: . 30 | 31 | cv-corveg:cover-methods a skos:ConceptScheme ; 32 | skos:prefLabel "Cover methods" ; 33 | rdfs:comment "Vocabulary of cover method terms used in CORVEG." ; 34 | skos:altLabel "Cov. methods", "Cov. meth.", "C. M.", "COVER METHODS" ; 35 | dcterms:created "2019-04-10"^^xsd:dateTime ; 36 | dcterms:creator "e.chuc@uq.edu.au" ; 37 | . 38 | 39 | cv-corveg:cover-method-i a skos:Concept, 40 | sosa:Procedure ; 41 | skos:definition "Line intercept" ; 42 | skos:hiddenLabel "3" ; 43 | skos:notation "I" ; 44 | skos:prefLabel "Line intercept" ; 45 | skos:inScheme cv-corveg:cover-methods ; 46 | dcterms:bibliographicCitation "Neldner, V.J., Wilson, B.A., Dillewaard, H.A., Ryan, T.S. and Butler, D.W. (2017) Methodology for Survey and Mapping of Regional Ecosystems and Vegetation Communities in Queensland. Version 4.0. Updated May 2017. Queensland Herbarium, Queensland Department of Science, Information Technology and Innovation, Brisbane. 124 pp." ; 47 | skos:topConceptOf cv-corveg:cover-methods ; 48 | skos:exactMatch ; 49 | . 50 | 51 | cv-corveg:cover-method-l a skos:Concept, 52 | sosa:Procedure ; 53 | skos:definition "Photographic (e.g. fish-eye lens)" ; 54 | skos:hiddenLabel "1" ; 55 | skos:notation "L" ; 56 | skos:prefLabel "Photographic" ; 57 | skos:inScheme cv-corveg:cover-methods ; 58 | dcterms:bibliographicCitation "Neldner, V.J., Wilson, B.A., Dillewaard, H.A., Ryan, T.S. and Butler, D.W. (2017) Methodology for Survey and Mapping of Regional Ecosystems and Vegetation Communities in Queensland. Version 4.0. Updated May 2017. Queensland Herbarium, Queensland Department of Science, Information Technology and Innovation, Brisbane. 124 pp." ; 59 | skos:topConceptOf cv-corveg:cover-methods ; 60 | . 61 | 62 | cv-corveg:cover-methods-unrecorded a skos:Concept, 63 | sosa:Procedure ; 64 | skos:definition "Unrecorded." ; 65 | skos:hiddenLabel "999" ; 66 | skos:notation "null" ; 67 | skos:prefLabel "Unrecorded" ; 68 | skos:inScheme cv-corveg:cover-methods ; 69 | dcterms:bibliographicCitation "Neldner, V.J., Wilson, B.A., Dillewaard, H.A., Ryan, T.S. and Butler, D.W. (2017) Methodology for Survey and Mapping of Regional Ecosystems and Vegetation Communities in Queensland. Version 4.0. Updated May 2017. Queensland Herbarium, Queensland Department of Science, Information Technology and Innovation, Brisbane. 124 pp." ; 70 | skos:topConceptOf cv-corveg:cover-methods ; 71 | . 72 | 73 | cv-corveg:cover-method-v a skos:Concept, 74 | sosa:Procedure ; 75 | skos:definition "Visual estimate of cover in subordinate layers" ; 76 | skos:hiddenLabel "2" ; 77 | skos:notation "V" ; 78 | skos:prefLabel "Visual estimate" ; 79 | skos:inScheme cv-corveg:cover-methods ; 80 | dcterms:bibliographicCitation "Neldner, V.J., Wilson, B.A., Dillewaard, H.A., Ryan, T.S. and Butler, D.W. (2017) Methodology for Survey and Mapping of Regional Ecosystems and Vegetation Communities in Queensland. Version 4.0. Updated May 2017. Queensland Herbarium, Queensland Department of Science, Information Technology and Innovation, Brisbane. 124 pp." ; 81 | skos:topConceptOf cv-corveg:cover-methods ; 82 | . -------------------------------------------------------------------------------- /local_vocabs/disturbance-assessment.ttl: -------------------------------------------------------------------------------- 1 | # baseURI: http://linked.data.gov.au/cv/corveg/corveg-methods 2 | # imports: http://www.w3.org/2004/02/skos/core 3 | # imports: http://www.w3.org/ns/sosa/ 4 | 5 | @prefix corveg: . 6 | @prefix cv-corveg: . 7 | @prefix data: . 8 | @prefix dct: . 9 | @prefix dcterms: . 10 | @prefix locn: . 11 | @prefix ogroup: . 12 | @prefix op: . 13 | @prefix owl: . 14 | @prefix plot: . 15 | @prefix prov: . 16 | @prefix rdf: . 17 | @prefix rdfs: . 18 | @prefix site-corveg: . 19 | @prefix site-strata-corveg: . 20 | @prefix skos: . 21 | @prefix sosa: . 22 | @prefix ssn-ext: . 23 | @prefix unit: . 24 | @prefix xml: . 25 | @prefix xsd: . 26 | 27 | cv-corveg:corveg-methods 28 | a owl:Ontology ; 29 | owl:imports ; 30 | owl:imports sosa: ; 31 | . 32 | cv-corveg:disturbance-assessment-methods 33 | a skos:ConceptScheme ; 34 | skos:prefLabel "Disturbance Assessment Methods" ; 35 | rdfs:comment "Description of disturbance assessment methods used in CORVEG Methodology." ; 36 | . 37 | cv-corveg:methods-disturbance-assessment 38 | a skos:Concept ; 39 | a sosa:Procedure ; 40 | skos:inScheme cv-corveg:disturbance-assessment-methods ; 41 | skos:topConceptOf cv-corveg:disturbance-assessment-methods ; 42 | dct:source "Methodology for Survey and Mapping of Regional Ecosystems and Vegetation Communities in Queensland Version 5.0" ; 43 | dcterms:bibliographicCitation "Neldner, V.J., Wilson, B.A., Dillewaard, H.A., Ryan, T.S., Butler, D.W., McDonald, W.J.F, Addicott,E.P. and Appelman, C.N. (2019) Methodology for survey and mapping of regional ecosystems and vegetation communities in Queensland. Version 5.0. Updated March 2019. Queensland Herbarium, Queensland Department of Environment and Science, Brisbane." ; 44 | rdfs:isDefinedBy ; 45 | skos:definition """The disturbance data are designed to record whether the site may be unrepresentative because they have been subject to abnormal disturbance. Therefore, generally sites will only be located in a disturbed area where no undisturbed sites could be located. Disturbance abundance estimates are made for the proportion of the disturbance occurring within the 10 m to 50 m quadrat into the classes listed in Table 14. These fields are left blank where disturbances are absent. 46 | 47 | ###### Storm damage 48 | - This is identifiable by the presence of broken branches in the crowns. Estimate the proportion of 49 | crown cover damaged on the site and the age of the damage using Table 14. 50 | 51 | ###### Logging 52 | - Record the number of stumps in the site. 53 | 54 | ###### Ringbarking, poisoning, thinning 55 | - Record the number of stems in the site. 56 | 57 | ###### Grazing 58 | - Grazing by domestic stock, feral animals and native animals evident in damage to the ground layer 59 | plants and/or presence of animal faeces or tracks is recorded as: 60 | - not apparent 61 | - present 62 | - severe (enough to make major impact on ground cover abundance or composition). 63 | 64 | ###### Extensive clearing 65 | A record of if the site has been previously cleared/thinned and regrown. Recorded as: 66 | 67 | - not apparent 68 | - present (i.e. most of the vegetation is regrowth from a previous clearing/thinning event). 69 | 70 | ###### Animal diggings 71 | These are recorded as: 72 | 73 | - not apparent 74 | - present. 75 | 76 | ###### Roadworks 77 | Record old snig tracks and other tracks; record proportion and age of the site affected using Table 14. 78 | 79 | ###### Salinity 80 | Record the proportion of the site affected by severe anthropogenic-caused salinity. 81 | 82 | ###### Fire 83 | Record the proportion of the site burnt, age (from above) and tallest vegetation impacted by fire from table 15. 84 | 85 | ###### Weeds 86 | The percentage cover of exotic species at the site is estimated. In the case of secondary sites this 87 | figure is derived by adding the cover of weed species from the comprehensive species list (the cover values are added ignoring any overlap between strata). 88 | 89 | ###### Erosion 90 | An estimate of the area as a proportion (Table 14) and type and severity of accelerated erosion (Table 16: as compared to natural erosion as discussed by McDonald, Isbell and Speight 2009) is recorded.""" ; 91 | skos:prefLabel "Disturbance Assessment Method" ; 92 | skos:closeMatch ; 93 | skos:closeMatch ; 94 | skos:closeMatch ; 95 | . -------------------------------------------------------------------------------- /local_vocabs/skos.ttl: -------------------------------------------------------------------------------- 1 | @prefix owl: . 2 | @prefix dc: . 3 | @prefix rdfs: . 4 | @prefix skos: . 5 | @prefix rdf: . 6 | 7 | 8 | a owl:Ontology ; 9 | dc:title "SKOS Vocabulary"@en ; 10 | dc:contributor "Dave Beckett", "Nikki Rogers", "Participants in W3C's Semantic Web Deployment Working Group." ; 11 | dc:description "An RDF vocabulary for describing the basic structure and content of concept schemes such as thesauri, classification schemes, subject heading lists, taxonomies, 'folksonomies', other types of controlled vocabulary, and also concept schemes embedded in glossaries and terminologies."@en ; 12 | dc:creator "Alistair Miles", "Sean Bechhofer" ; 13 | rdfs:seeAlso . 14 | 15 | skos:Concept 16 | rdfs:label "Concept"@en ; 17 | rdfs:isDefinedBy ; 18 | skos:definition "An idea or notion; a unit of thought."@en ; 19 | a owl:Class . 20 | 21 | skos:ConceptScheme 22 | rdfs:label "Concept Scheme"@en ; 23 | rdfs:isDefinedBy ; 24 | skos:definition "A set of concepts, optionally including statements about semantic relationships between those concepts."@en ; 25 | skos:scopeNote "A concept scheme may be defined to include concepts from different sources."@en ; 26 | skos:example "Thesauri, classification schemes, subject heading lists, taxonomies, 'folksonomies', and other types of controlled vocabulary are all examples of concept schemes. Concept schemes are also embedded in glossaries and terminologies."@en ; 27 | a owl:Class ; 28 | owl:disjointWith skos:Concept . 29 | 30 | skos:Collection 31 | rdfs:label "Collection"@en ; 32 | rdfs:isDefinedBy ; 33 | skos:definition "A meaningful collection of concepts."@en ; 34 | skos:scopeNote "Labelled collections can be used where you would like a set of concepts to be displayed under a 'node label' in the hierarchy."@en ; 35 | a owl:Class ; 36 | owl:disjointWith skos:Concept, skos:ConceptScheme . 37 | 38 | skos:OrderedCollection 39 | rdfs:label "Ordered Collection"@en ; 40 | rdfs:isDefinedBy ; 41 | skos:definition "An ordered collection of concepts, where both the grouping and the ordering are meaningful."@en ; 42 | skos:scopeNote "Ordered collections can be used where you would like a set of concepts to be displayed in a specific order, and optionally under a 'node label'."@en ; 43 | a owl:Class ; 44 | rdfs:subClassOf skos:Collection . 45 | 46 | skos:inScheme 47 | rdfs:label "is in scheme"@en ; 48 | rdfs:isDefinedBy ; 49 | skos:definition "Relates a resource (for example a concept) to a concept scheme in which it is included."@en ; 50 | skos:scopeNote "A concept may be a member of more than one concept scheme."@en ; 51 | a owl:ObjectProperty, rdf:Property ; 52 | rdfs:range skos:ConceptScheme . 53 | 54 | skos:hasTopConcept 55 | rdfs:label "has top concept"@en ; 56 | rdfs:isDefinedBy ; 57 | skos:definition "Relates, by convention, a concept scheme to a concept which is topmost in the broader/narrower concept hierarchies for that scheme, providing an entry point to these hierarchies."@en ; 58 | a owl:ObjectProperty, rdf:Property ; 59 | rdfs:domain skos:ConceptScheme ; 60 | rdfs:range skos:Concept ; 61 | owl:inverseOf skos:topConceptOf . 62 | 63 | skos:topConceptOf 64 | rdfs:label "is top concept in scheme"@en ; 65 | rdfs:isDefinedBy ; 66 | skos:definition "Relates a concept to the concept scheme that it is a top level concept of."@en ; 67 | a owl:ObjectProperty, rdf:Property ; 68 | rdfs:subPropertyOf skos:inScheme ; 69 | owl:inverseOf skos:hasTopConcept ; 70 | rdfs:domain skos:Concept ; 71 | rdfs:range skos:ConceptScheme . 72 | 73 | skos:prefLabel 74 | rdfs:label "preferred label"@en ; 75 | rdfs:isDefinedBy ; 76 | skos:definition "The preferred lexical label for a resource, in a given language."@en ; 77 | a owl:AnnotationProperty, rdf:Property ; 78 | # rdfs:subPropertyOf rdfs:label ; 79 | rdfs:comment "A resource has no more than one value of skos:prefLabel per language tag, and no more than one value of skos:prefLabel without language tag."@en, "The range of skos:prefLabel is the class of RDF plain literals."@en, """skos:prefLabel, skos:altLabel and skos:hiddenLabel are pairwise 80 | disjoint properties."""@en . 81 | 82 | skos:altLabel 83 | rdfs:label "alternative label"@en ; 84 | rdfs:isDefinedBy ; 85 | skos:definition "An alternative lexical label for a resource."@en ; 86 | skos:example "Acronyms, abbreviations, spelling variants, and irregular plural/singular forms may be included among the alternative labels for a concept. Mis-spelled terms are normally included as hidden labels (see skos:hiddenLabel)."@en ; 87 | a owl:AnnotationProperty, rdf:Property ; 88 | # rdfs:subPropertyOf rdfs:label ; 89 | rdfs:comment "The range of skos:altLabel is the class of RDF plain literals."@en, "skos:prefLabel, skos:altLabel and skos:hiddenLabel are pairwise disjoint properties."@en . 90 | 91 | skos:hiddenLabel 92 | rdfs:label "hidden label"@en ; 93 | rdfs:isDefinedBy ; 94 | skos:definition "A lexical label for a resource that should be hidden when generating visual displays of the resource, but should still be accessible to free text search operations."@en ; 95 | a owl:AnnotationProperty, rdf:Property ; 96 | # rdfs:subPropertyOf rdfs:label ; 97 | rdfs:comment "The range of skos:hiddenLabel is the class of RDF plain literals."@en, "skos:prefLabel, skos:altLabel and skos:hiddenLabel are pairwise disjoint properties."@en . 98 | 99 | skos:notation 100 | rdfs:label "notation"@en ; 101 | rdfs:isDefinedBy ; 102 | skos:definition "A notation, also known as classification code, is a string of characters such as \"T58.5\" or \"303.4833\" used to uniquely identify a concept within the scope of a given concept scheme."@en ; 103 | skos:scopeNote "By convention, skos:notation is used with a typed literal in the object position of the triple."@en ; 104 | a owl:DatatypeProperty, rdf:Property . 105 | 106 | skos:note 107 | rdfs:label "note"@en ; 108 | rdfs:isDefinedBy ; 109 | skos:definition "A general note, for any purpose."@en ; 110 | skos:scopeNote "This property may be used directly, or as a super-property for more specific note types."@en ; 111 | a owl:AnnotationProperty, rdf:Property . 112 | 113 | skos:changeNote 114 | rdfs:label "change note"@en ; 115 | rdfs:isDefinedBy ; 116 | skos:definition "A note about a modification to a concept."@en ; 117 | a owl:AnnotationProperty, rdf:Property ; 118 | # rdfs:subPropertyOf skos:note ; 119 | . 120 | 121 | skos:definition 122 | rdfs:label "definition"@en ; 123 | rdfs:isDefinedBy ; 124 | skos:definition "A statement or formal explanation of the meaning of a concept."@en ; 125 | a owl:AnnotationProperty, rdf:Property ; 126 | # rdfs:subPropertyOf skos:note ; 127 | . 128 | 129 | skos:editorialNote 130 | rdfs:label "editorial note"@en ; 131 | rdfs:isDefinedBy ; 132 | skos:definition "A note for an editor, translator or maintainer of the vocabulary."@en ; 133 | a owl:AnnotationProperty, rdf:Property ; 134 | # rdfs:subPropertyOf skos:note ; 135 | . 136 | 137 | skos:example 138 | rdfs:label "example"@en ; 139 | rdfs:isDefinedBy ; 140 | skos:definition "An example of the use of a concept."@en ; 141 | a owl:AnnotationProperty, rdf:Property ; 142 | # rdfs:subPropertyOf skos:note ; 143 | . 144 | 145 | skos:historyNote 146 | rdfs:label "history note"@en ; 147 | rdfs:isDefinedBy ; 148 | skos:definition "A note about the past state/use/meaning of a concept."@en ; 149 | a owl:AnnotationProperty, rdf:Property ; 150 | # rdfs:subPropertyOf skos:note ; 151 | . 152 | 153 | skos:scopeNote 154 | rdfs:label "scope note"@en ; 155 | rdfs:isDefinedBy ; 156 | skos:definition "A note that helps to clarify the meaning and/or the use of a concept."@en ; 157 | a owl:AnnotationProperty, rdf:Property ; 158 | # rdfs:subPropertyOf skos:note ; 159 | . 160 | 161 | skos:semanticRelation 162 | rdfs:label "is in semantic relation with"@en ; 163 | rdfs:isDefinedBy ; 164 | skos:definition "Links a concept to a concept related by meaning."@en ; 165 | skos:scopeNote "This property should not be used directly, but as a super-property for all properties denoting a relationship of meaning between concepts."@en ; 166 | a owl:ObjectProperty, rdf:Property ; 167 | rdfs:domain skos:Concept ; 168 | rdfs:range skos:Concept . 169 | 170 | skos:broader 171 | rdfs:label "has broader"@en ; 172 | rdfs:isDefinedBy ; 173 | skos:definition "Relates a concept to a concept that is more general in meaning."@en ; 174 | rdfs:comment "Broader concepts are typically rendered as parents in a concept hierarchy (tree)."@en ; 175 | skos:scopeNote "By convention, skos:broader is only used to assert an immediate (i.e. direct) hierarchical link between two conceptual resources."@en ; 176 | a owl:ObjectProperty, rdf:Property ; 177 | # rdfs:subPropertyOf skos:broaderTransitive ; 178 | owl:inverseOf skos:narrower . 179 | 180 | skos:narrower 181 | rdfs:label "has narrower"@en ; 182 | rdfs:isDefinedBy ; 183 | skos:definition "Relates a concept to a concept that is more specific in meaning."@en ; 184 | skos:scopeNote "By convention, skos:broader is only used to assert an immediate (i.e. direct) hierarchical link between two conceptual resources."@en ; 185 | rdfs:comment "Narrower concepts are typically rendered as children in a concept hierarchy (tree)."@en ; 186 | a owl:ObjectProperty, rdf:Property ; 187 | # rdfs:subPropertyOf skos:narrowerTransitive ; 188 | owl:inverseOf skos:broader . 189 | 190 | skos:related 191 | rdfs:label "has related"@en ; 192 | rdfs:isDefinedBy ; 193 | skos:definition "Relates a concept to a concept with which there is an associative semantic relationship."@en ; 194 | a owl:ObjectProperty, owl:SymmetricProperty, rdf:Property ; 195 | rdfs:subPropertyOf skos:semanticRelation ; 196 | rdfs:comment "skos:related is disjoint with skos:broaderTransitive"@en . 197 | 198 | skos:broaderTransitive 199 | rdfs:label "has broader transitive"@en ; 200 | rdfs:isDefinedBy ; 201 | skos:definition "skos:broaderTransitive is a transitive superproperty of skos:broader." ; 202 | skos:scopeNote "By convention, skos:broaderTransitive is not used to make assertions. Rather, the properties can be used to draw inferences about the transitive closure of the hierarchical relation, which is useful e.g. when implementing a simple query expansion algorithm in a search application."@en ; 203 | a owl:ObjectProperty, owl:TransitiveProperty, rdf:Property ; 204 | rdfs:subPropertyOf skos:semanticRelation ; 205 | owl:inverseOf skos:narrowerTransitive . 206 | 207 | skos:narrowerTransitive 208 | rdfs:label "has narrower transitive"@en ; 209 | rdfs:isDefinedBy ; 210 | skos:definition "skos:narrowerTransitive is a transitive superproperty of skos:narrower." ; 211 | skos:scopeNote "By convention, skos:narrowerTransitive is not used to make assertions. Rather, the properties can be used to draw inferences about the transitive closure of the hierarchical relation, which is useful e.g. when implementing a simple query expansion algorithm in a search application."@en ; 212 | a owl:ObjectProperty, owl:TransitiveProperty, rdf:Property ; 213 | rdfs:subPropertyOf skos:semanticRelation ; 214 | owl:inverseOf skos:broaderTransitive . 215 | 216 | skos:member 217 | rdfs:label "has member"@en ; 218 | rdfs:isDefinedBy ; 219 | skos:definition "Relates a collection to one of its members."@en ; 220 | a owl:ObjectProperty, rdf:Property ; 221 | rdfs:domain skos:Collection ; 222 | rdfs:range [ 223 | a owl:Class ; 224 | owl:unionOf ( 225 | skos:Concept 226 | skos:Collection 227 | ) 228 | ] . 229 | 230 | skos:memberList 231 | rdfs:label "has member list"@en ; 232 | rdfs:isDefinedBy ; 233 | skos:definition "Relates an ordered collection to the RDF list containing its members."@en ; 234 | a owl:ObjectProperty, owl:FunctionalProperty, rdf:Property ; 235 | rdfs:domain skos:OrderedCollection ; 236 | rdfs:range rdf:List ; 237 | rdfs:comment """For any resource, every item in the list given as the value of the 238 | skos:memberList property is also a value of the skos:member property."""@en . 239 | 240 | skos:mappingRelation 241 | rdfs:label "is in mapping relation with"@en ; 242 | rdfs:isDefinedBy ; 243 | skos:definition "Relates two concepts coming, by convention, from different schemes, and that have comparable meanings"@en ; 244 | rdfs:comment "These concept mapping relations mirror semantic relations, and the data model defined below is similar (with the exception of skos:exactMatch) to the data model defined for semantic relations. A distinct vocabulary is provided for concept mapping relations, to provide a convenient way to differentiate links within a concept scheme from links between concept schemes. However, this pattern of usage is not a formal requirement of the SKOS data model, and relies on informal definitions of best practice."@en ; 245 | a owl:ObjectProperty, rdf:Property ; 246 | # rdfs:subPropertyOf skos:semanticRelation ; 247 | . 248 | 249 | skos:broadMatch 250 | rdfs:label "has broader match"@en ; 251 | rdfs:isDefinedBy ; 252 | skos:definition "skos:broadMatch is used to state a hierarchical mapping link between two conceptual resources in different concept schemes."@en ; 253 | a owl:ObjectProperty, rdf:Property ; 254 | rdfs:subPropertyOf skos:mappingRelation, skos:broader ; 255 | owl:inverseOf skos:narrowMatch . 256 | 257 | skos:narrowMatch 258 | rdfs:label "has narrower match"@en ; 259 | rdfs:isDefinedBy ; 260 | skos:definition "skos:narrowMatch is used to state a hierarchical mapping link between two conceptual resources in different concept schemes."@en ; 261 | a owl:ObjectProperty, rdf:Property ; 262 | rdfs:subPropertyOf skos:mappingRelation, skos:narrower ; 263 | owl:inverseOf skos:broadMatch . 264 | 265 | skos:relatedMatch 266 | rdfs:label "has related match"@en ; 267 | rdfs:isDefinedBy ; 268 | skos:definition "skos:relatedMatch is used to state an associative mapping link between two conceptual resources in different concept schemes."@en ; 269 | a owl:ObjectProperty, owl:SymmetricProperty, rdf:Property ; 270 | # rdfs:subPropertyOf skos:mappingRelation, skos:related ; 271 | . 272 | 273 | skos:exactMatch 274 | rdfs:label "has exact match"@en ; 275 | rdfs:isDefinedBy ; 276 | skos:definition "skos:exactMatch is used to link two concepts, indicating a high degree of confidence that the concepts can be used interchangeably across a wide range of information retrieval applications. skos:exactMatch is a transitive property, and is a sub-property of skos:closeMatch."@en ; 277 | a owl:ObjectProperty, owl:SymmetricProperty, owl:TransitiveProperty, rdf:Property ; 278 | # rdfs:subPropertyOf skos:closeMatch ; 279 | rdfs:comment "skos:exactMatch is disjoint with each of the properties skos:broadMatch and skos:relatedMatch."@en . 280 | 281 | skos:closeMatch 282 | rdfs:label "has close match"@en ; 283 | rdfs:isDefinedBy ; 284 | skos:definition "skos:closeMatch is used to link two concepts that are sufficiently similar that they can be used interchangeably in some information retrieval applications. In order to avoid the possibility of \"compound errors\" when combining mappings across more than two concept schemes, skos:closeMatch is not declared to be a transitive property."@en ; 285 | a owl:ObjectProperty, owl:SymmetricProperty, rdf:Property ; 286 | # rdfs:subPropertyOf skos:mappingRelation ; 287 | . 288 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.9.0 2 | certifi==2020.4.5.1 3 | chardet==3.0.4 4 | click==7.1.1 5 | connegp==0.2 6 | Flask==1.1.2 7 | flask-paginate==0.5.5 8 | idna==2.9 9 | isodate==0.6.0 10 | itsdangerous==1.1.0 11 | Jinja2==2.11.3 12 | Markdown==3.2.1 13 | MarkupSafe==1.1.1 14 | munch==2.5.0 15 | owlrl==5.2.1 16 | pyldapi==2.1.4 17 | pyparsing==2.4.7 18 | PyYAML==5.4 19 | rdflib==5.0.0 20 | rdflib-jsonld==0.5.0 21 | requests==2.25.1 22 | six==1.14.0 23 | soupsieve==2.0 24 | SPARQLWrapper==1.8.5 25 | urllib3==1.26.5 26 | Werkzeug==0.16.0 27 | python-dotenv==0.15.0 28 | Flask-Cors==3.0.10 29 | watchdog==2.1.2 30 | celery==5.1.1 31 | https://bitbucket.org/terndatateam/tern_rdf/get/0.0.33.zip -------------------------------------------------------------------------------- /skos/__init__.py: -------------------------------------------------------------------------------- 1 | from rdflib.namespace import RDF, SKOS, DCTERMS, RDFS, OWL, DC 2 | from rdflib import URIRef, Namespace, Literal, Graph 3 | import markdown 4 | from flask import url_for 5 | import requests 6 | 7 | from config import Config 8 | from skos.concept_scheme import ConceptScheme, ConceptSchemeRenderer 9 | from skos.concept import Concept, ConceptRenderer 10 | from skos.collection import CollectionRenderer, Collection 11 | from skos.register import Register 12 | import helper 13 | 14 | from datetime import date 15 | from urllib import parse 16 | 17 | 18 | # Controlled values 19 | CONCEPT = 0 20 | CONCEPTSCHEME = 1 21 | COLLECTION = 2 22 | METHOD = 3 23 | 24 | SCHEMAORG = Namespace('http://schema.org/') 25 | 26 | 27 | def list_concepts(): 28 | concepts = [] 29 | for c in Config.g.subjects(RDF.type, SKOS.Concept): 30 | label = get_label(c) 31 | date_created = get_created_date(c) 32 | date_modified = get_modified_date(c) 33 | definition = get_definition(c) 34 | scheme = get_in_scheme(c) 35 | concepts.append((c, label, [ 36 | (URIRef('http://purl.org/dc/terms/created'), date_created), 37 | (URIRef('http://purl.org/dc/terms/modified'), date_modified), 38 | (URIRef('http://www.w3.org/2004/02/skos/core#definition'), definition), 39 | (URIRef('http://www.w3.org/2004/02/skos/core#inScheme'), scheme) 40 | ])) 41 | return sorted(concepts, key=lambda i: i[1]) 42 | 43 | 44 | def list_concept_schemes(): 45 | concept_schemes = [] 46 | 47 | for cc in Config.g.subjects(RDF.type, SKOS.ConceptScheme): 48 | label = get_label(cc) 49 | date_created = get_created_date(cc) 50 | date_modified = get_modified_date(cc) 51 | description = get_description(cc) 52 | concept_schemes.append((cc, label, [ 53 | (URIRef('http://purl.org/dc/terms/created'), date_created), 54 | (URIRef('http://purl.org/dc/terms/modified'), date_modified), 55 | description 56 | ])) 57 | 58 | return sorted(concept_schemes, key=lambda i: i[1]) 59 | 60 | 61 | def list_concept_schemes_and_collections(): 62 | items = [] 63 | 64 | for cc in Config.g.subjects(RDF.type, SKOS.ConceptScheme): 65 | if not is_deprecated(cc): 66 | label = get_label(cc) 67 | date_created = get_created_date(cc) 68 | date_modified = get_modified_date(cc) 69 | description = get_description(cc) 70 | items.append((cc, label, [ 71 | (URIRef('http://purl.org/dc/terms/created'), date_created), 72 | (URIRef('http://purl.org/dc/terms/modified'), date_modified), 73 | description 74 | ])) 75 | 76 | for cc in Config.g.subjects(RDF.type, SKOS.Collection): 77 | if not is_deprecated(cc): 78 | label = get_label(cc) 79 | date_created = get_created_date(cc) 80 | date_modified = get_modified_date(cc) 81 | description = get_description(cc) 82 | items.append((cc, label, [ 83 | (URIRef('http://purl.org/dc/terms/created'), date_created), 84 | (URIRef('http://purl.org/dc/terms/modified'), date_modified), 85 | description 86 | ])) 87 | 88 | return sorted(items, key=lambda i: i[1]) 89 | 90 | 91 | def _split_camel_case_label(label): 92 | new_label = '' 93 | last = 0 94 | for i, letter in enumerate(label): 95 | if letter.isupper(): 96 | new_label += ' {}'.format(label[last:i]) 97 | last = i 98 | 99 | new_label += ' {}'.format(label[last:]) 100 | new_label = new_label.strip() 101 | return new_label 102 | 103 | 104 | def get_label(uri, create=True): 105 | # TODO: title() capitalises all words, we need a post-process function to lower case words that are of types 106 | # such as preposition and conjunction. 107 | for label in Config.g.objects(URIRef(uri), SKOS.prefLabel): 108 | return label 109 | for label in Config.g.objects(URIRef(uri), DCTERMS.title): 110 | return label 111 | for label in Config.g.objects(URIRef(uri), RDFS.label): 112 | return label 113 | 114 | # Fetch label by dereferencing URI. 115 | if create: 116 | headers = {'accept': 'text/turtle'} 117 | response_g = Graph() 118 | try: 119 | r = requests.get(uri, headers=headers) 120 | assert 200 <= r.status_code < 300 121 | response_g.parse(data=r.content.decode('utf-8'), format='turtle') 122 | for _, _, label in response_g.triples((uri, SKOS.prefLabel, None)): 123 | return label 124 | for _, _, label in response_g.triples((uri, RDFS.label, None)): 125 | return label 126 | except Exception as e: 127 | # print(uri) 128 | # print('Error dereferencing external URI:', str(e)) 129 | # print(r.content.decode('utf-8')) 130 | # print('Create label from the local name of the URI instead.') 131 | 132 | # Create label out of the local segment of the URI. 133 | label = helper.uri_label(uri) 134 | label = _split_camel_case_label(label) 135 | return Literal(label) 136 | else: 137 | return Literal(str(uri).split('#')[-1].split('/')[-1]) 138 | 139 | 140 | def get_description(uri): 141 | for description in Config.g.objects(URIRef(uri), DCTERMS.description): 142 | return (DCTERMS.description, description) 143 | for description in Config.g.objects(URIRef(uri), DC.description): 144 | return (DC.description, description) 145 | for description in Config.g.objects(URIRef(uri), RDFS.comment): 146 | return (RDFS.comment, description) 147 | 148 | 149 | def get_definition(uri): 150 | for definition in Config.g.objects(URIRef(uri), SKOS.definition): 151 | return definition 152 | 153 | 154 | def get_class_types(uri): 155 | types = [] 156 | for type in Config.g.objects(URIRef(uri), RDF.type): 157 | # Only add URIs (and not blank nodes!) 158 | if str(type)[:4] == 'http' \ 159 | and str(type) != 'http://www.w3.org/2004/02/skos/core#ConceptScheme' \ 160 | and str(type) != 'http://www.w3.org/2004/02/skos/core#Concept' \ 161 | and str(type) != 'http://www.w3.org/2004/02/skos/core#Collection': 162 | types.append(type) 163 | return types 164 | 165 | 166 | def is_deprecated(uri): 167 | for value in Config.g.objects(URIRef(uri), OWL.deprecated): 168 | return bool(value) 169 | return False 170 | 171 | 172 | def get_narrowers(uri): 173 | narrowers = [] 174 | for narrower in Config.g.objects(URIRef(uri), SKOS.narrower): 175 | if not is_deprecated(narrower): 176 | label = get_label(narrower) 177 | narrowers.append((narrower, label)) 178 | return sorted(narrowers, key=lambda i: i[1]) 179 | 180 | 181 | def get_broaders(uri): 182 | broaders = [] 183 | for broader in Config.g.objects(URIRef(uri), SKOS.broader): 184 | if not is_deprecated(broader): 185 | label = get_label(broader) 186 | broaders.append((broader, label)) 187 | return sorted(broaders, key=lambda i: i[1]) 188 | 189 | 190 | def get_members(uri): 191 | members = [] 192 | for member in Config.g.objects(URIRef(uri), SKOS.member): 193 | label = get_label(member) 194 | members.append((member, label)) 195 | return sorted(members, key=lambda i: i[1]) 196 | 197 | 198 | def get_top_concept_of(uri): 199 | top_concept_ofs = [] 200 | for tco in Config.g.objects(URIRef(uri), SKOS.topConceptOf): 201 | label = get_label(tco) 202 | top_concept_ofs.append((tco, label)) 203 | return sorted(top_concept_ofs, key=lambda i: i[1]) 204 | 205 | 206 | def get_top_concepts(uri): 207 | top_concepts = [] 208 | for tc in Config.g.objects(URIRef(uri), SKOS.hasTopConcept): 209 | label = get_label(tc) 210 | top_concepts.append((tc, label)) 211 | return sorted(top_concepts, key=lambda i: i[1]) 212 | 213 | 214 | def get_change_note(uri): 215 | for cn in Config.g.objects(URIRef(uri), SKOS.changeNote): 216 | return cn 217 | 218 | 219 | def get_alt_labels(uri): 220 | labels = [] 221 | for alt_label in Config.g.objects(URIRef(uri), SKOS.altLabel): 222 | labels.append(alt_label) 223 | return sorted(labels) 224 | 225 | 226 | def get_created_date(uri): 227 | for created in Config.g.objects(URIRef(uri), DCTERMS.created): 228 | created = created.split('-') 229 | created = date(int(created[0]), int(created[1]), int(created[2][:2])) 230 | return created 231 | 232 | 233 | def get_modified_date(uri): 234 | for modified in Config.g.objects(URIRef(uri), DCTERMS.modified): 235 | modified = modified.split('-') 236 | modified = date(int(modified[0]), int(modified[1]), int(modified[2][:2])) 237 | return modified 238 | 239 | 240 | def get_uri_skos_type(uri): 241 | uri = parse.unquote_plus(uri) 242 | for _ in Config.g.triples((URIRef(uri), RDF.type, URIRef('https://w3id.org/tern/ontologies/tern/Method'))): 243 | return METHOD 244 | for _ in Config.g.triples((URIRef(uri), RDF.type, SKOS.ConceptScheme)): 245 | return CONCEPTSCHEME 246 | for _ in Config.g.triples((URIRef(uri), RDF.type, SKOS.Concept)): 247 | return CONCEPT 248 | for _ in Config.g.triples((URIRef(uri), RDF.type, SKOS.Collection)): 249 | return COLLECTION 250 | return None 251 | 252 | 253 | def get_properties(uri): 254 | ignore = [ 255 | # Common 256 | RDF.type, SKOS.prefLabel, DCTERMS.title, RDFS.label, DCTERMS.description, SKOS.definition, SKOS.changeNote, 257 | DCTERMS.created, DCTERMS.modified, OWL.sameAs, RDFS.comment, SKOS.altLabel, DCTERMS.bibliographicCitation, 258 | RDFS.isDefinedBy, DC.description, DCTERMS.creator, DCTERMS.contributor, SCHEMAORG.parentOrganization, 259 | SCHEMAORG.contactPoint, SCHEMAORG.member, SCHEMAORG.subOrganization, SCHEMAORG.familyName, 260 | URIRef('http://schema.semantic-web.at/ppt/propagateType'), SCHEMAORG.givenName, SCHEMAORG.honorificPrefix, 261 | SCHEMAORG.jobTitle, SCHEMAORG.memberOf, URIRef('http://schema.semantic-web.at/ppt/appliedType'), SKOS.member, 262 | 263 | # Concept 264 | SKOS.narrower, SKOS.broader, SKOS.topConceptOf, SKOS.inScheme, SKOS.closeMatch, SKOS.exactMatch, 265 | 266 | # Concept Scheme 267 | SKOS.hasTopConcept 268 | ] 269 | 270 | properties = [] 271 | for _, property, value in Config.g.triples((URIRef(uri), None, None)): 272 | if property in ignore: 273 | continue 274 | 275 | label = get_label(value, create=False) if type(value) == URIRef else None 276 | properties.append(((property, get_label(property, create=False)), value, label)) 277 | 278 | properties.sort(key=lambda x: x[0]) 279 | return properties 280 | 281 | 282 | def get_in_scheme(uri): 283 | """A concept scheme in which the concept is a part of. A concept may be a member of more than one concept scheme""" 284 | schemes = [] 285 | for scheme in Config.g.objects(URIRef(uri), SKOS.inScheme): 286 | label = get_label(scheme) 287 | schemes.append((scheme, label)) 288 | return schemes 289 | 290 | 291 | def _add_narrower(uri, hierarchy, indent): 292 | concepts = [] 293 | 294 | for concept in Config.g.objects(URIRef(uri), SKOS.narrower): 295 | if not is_deprecated(concept): 296 | label = get_label(concept) 297 | concepts.append((concept, label)) 298 | 299 | for concept in Config.g.objects(URIRef(uri), SKOS.member): 300 | if not is_deprecated(concept): 301 | label = get_label(concept) 302 | concepts.append((concept, label)) 303 | 304 | concepts.sort(key=lambda i: i[1]) 305 | 306 | for concept in concepts: 307 | tab = indent * '\t' 308 | hierarchy += tab + '- [{}]({})\n'.format(concept[1], url_for('routes.ob', uri=concept[0])) 309 | hierarchy = _add_narrower(concept[0], hierarchy, indent + 1) 310 | 311 | return hierarchy 312 | 313 | 314 | def get_concept_hierarchy_collection(uri): 315 | hierarchy = '' 316 | members = [] 317 | 318 | for concept_or_collection in Config.g.objects(URIRef(uri), SKOS.member): 319 | if not is_deprecated(concept_or_collection): 320 | label = get_label(concept_or_collection) 321 | members.append((concept_or_collection, label)) 322 | 323 | members.sort(key=lambda i: i[1]) 324 | 325 | for member in members: 326 | hierarchy += '- [{}]({})\n'.format(member[1], url_for('routes.ob', uri=member[0])) 327 | hierarchy = _add_narrower(member[0], hierarchy, 1) 328 | 329 | return '
' + markdown.markdown(hierarchy) + '
' 330 | 331 | 332 | def get_concept_hierarchy(uri): 333 | hierarchy = '' 334 | top_concepts = [] 335 | 336 | for top_concept in Config.g.objects(URIRef(uri), SKOS.hasTopConcept): 337 | if not is_deprecated(top_concept): 338 | label = get_label(top_concept) 339 | top_concepts.append((top_concept, label)) 340 | 341 | top_concepts.sort(key=lambda i: i[1]) 342 | 343 | for top_concept in top_concepts: 344 | hierarchy += '- [{}]({})\n'.format(top_concept[1], url_for('routes.ob', uri=top_concept[0])) 345 | hierarchy = _add_narrower(top_concept[0], hierarchy, 1) 346 | return '
' + markdown.markdown(hierarchy) + '
' 347 | 348 | 349 | def get_is_defined_by(uri): 350 | for is_def in Config.g.objects(URIRef(uri), RDFS.isDefinedBy): 351 | return is_def 352 | 353 | 354 | def get_close_match(uri): 355 | close_match = [] 356 | for cm in Config.g.objects(URIRef(uri), SKOS.closeMatch): 357 | close_match.append(cm) 358 | return close_match 359 | 360 | 361 | def get_exact_match(uri): 362 | exact_match = [] 363 | for em in Config.g.objects(URIRef(uri), SKOS.exactMatch): 364 | exact_match.append(em) 365 | return exact_match 366 | 367 | 368 | def get_bibliographic_citation(uri): 369 | for bg in Config.g.objects(URIRef(uri), DCTERMS.bibliographicCitation): 370 | return bg 371 | 372 | 373 | def get_dcterms_source(uri): 374 | for _, _, source in Config.g.triples((URIRef(uri), DCTERMS.source, None)): 375 | return source 376 | 377 | 378 | def get_schema_org_parent_org(uri): 379 | for parent_org in Config.g.objects(URIRef(uri), SCHEMAORG.parentOrganization): 380 | label = get_label(parent_org) 381 | return (parent_org, label) 382 | 383 | 384 | def get_schema_org_contact_point(uri): 385 | for cp in Config.g.objects(URIRef(uri), SCHEMAORG.contactPoint): 386 | label = get_label(cp) 387 | return (cp, label) 388 | 389 | 390 | def get_schema_org_members(uri): 391 | members = [] 392 | for m in Config.g.objects(URIRef(uri), SCHEMAORG.member): 393 | label = get_label(m) 394 | members.append((m, label)) 395 | return members 396 | 397 | 398 | def get_schema_org_sub_orgs(uri): 399 | orgs = [] 400 | for org in Config.g.objects(URIRef(uri), SCHEMAORG.subOrganization): 401 | label = get_label(org) 402 | orgs.append((org, label)) 403 | return orgs 404 | 405 | 406 | def get_schema_org_family_name(uri): 407 | for fn in Config.g.objects(URIRef(uri), SCHEMAORG.familyName): 408 | return fn 409 | 410 | 411 | def get_schema_org_given_name(uri): 412 | for gn in Config.g.objects(URIRef(uri), SCHEMAORG.givenName): 413 | return gn 414 | 415 | 416 | def get_schema_org_honorific_prefix(uri): 417 | for hp in Config.g.objects(URIRef(uri), SCHEMAORG.honorificPrefix): 418 | return hp 419 | 420 | 421 | def get_schema_org_job_title(uri): 422 | for jt in Config.g.objects(URIRef(uri), SCHEMAORG.jobTitle): 423 | return jt 424 | 425 | 426 | def get_schema_org_member_of(uri): 427 | for org in Config.g.objects(URIRef(uri), SCHEMAORG.memberOf): 428 | label = get_label(org) 429 | return (org, label) 430 | 431 | 432 | def member_of(uri): 433 | """ 434 | The inverse of skos:member - used for better UI navigation. 435 | """ 436 | collections = [] 437 | for collection in Config.g.subjects(SKOS.member, URIRef(uri)): 438 | label = get_label(collection) 439 | collections.append((collection, label)) 440 | return collections 441 | 442 | 443 | def get_creator(uri): 444 | for creator in Config.g.objects(URIRef(uri), DCTERMS.creator): 445 | return creator 446 | 447 | 448 | def get_rdf_predicate(uri): 449 | for predicate in Config.g.objects(URIRef(uri), RDF.predicate): 450 | return predicate 451 | 452 | 453 | def get_rdf_object(uri): 454 | for o in Config.g.objects(URIRef(uri), RDF.object): 455 | return o 456 | 457 | 458 | def get_mapping_statement(uri): 459 | uri = URIRef(uri) 460 | for statement in Config.g.subjects(RDF.type, RDF.Statement): 461 | for _, p, o in Config.g.triples((statement, None, None)): 462 | if p == RDF.subject and o == uri: 463 | return [ 464 | statement, 465 | get_rdf_predicate(statement), 466 | get_rdf_object(statement), 467 | get_created_date(statement), 468 | get_creator(statement), 469 | get_description(statement)[1], 470 | ] 471 | 472 | 473 | def get_method_purpose(uri): 474 | uri = URIRef(uri) 475 | for _, _, purpose in Config.g.triples((uri, URIRef('https://w3id.org/tern/ontologies/tern/purpose'), None)): 476 | return purpose 477 | 478 | 479 | def get_method_scope(uri): 480 | uri = URIRef(uri) 481 | for _, _, scope in Config.g.triples((uri, URIRef('https://w3id.org/tern/ontologies/tern/scope'), None)): 482 | return scope 483 | 484 | 485 | def get_method_equipment(uri): 486 | uri = URIRef(uri) 487 | equipments = [] 488 | for _, _, equipment in Config.g.triples((uri, URIRef('https://w3id.org/tern/ontologies/tern/equipment'), None)): 489 | if isinstance(equipment, URIRef): 490 | label = get_label(equipment) 491 | equipments.append((equipment, label)) 492 | else: 493 | return equipment 494 | return equipments 495 | 496 | 497 | def get_method_instructions(uri): 498 | uri = URIRef(uri) 499 | for _, _, instructions in Config.g.triples((uri, URIRef('https://w3id.org/tern/ontologies/tern/instructions'), None)): 500 | return instructions 501 | 502 | 503 | def get_parameter_relations(uri): 504 | uri = URIRef(uri) 505 | parameters = [] 506 | for _, _, parameter in Config.g.triples((uri, URIRef('https://w3id.org/tern/ontologies/tern/hasParameter'), None)): 507 | label = get_label(parameter) 508 | parameters.append((parameter, label)) 509 | 510 | return parameters 511 | 512 | 513 | def get_categorical_variables_relations(uri): 514 | uri = URIRef(uri) 515 | cvs = [] 516 | for _, _, cv in Config.g.triples((uri, URIRef('https://w3id.org/tern/ontologies/tern/hasCategoricalVariableCollection'), None)): 517 | label = get_label(cv) 518 | cvs.append((cv, label)) 519 | 520 | return cvs 521 | 522 | 523 | def get_method_time_required(uri): 524 | uri = URIRef(uri) 525 | for _, _, time_required in Config.g.triples((uri, URIRef('http://schema.org/timeRequired'), None)): 526 | return time_required 527 | 528 | 529 | def get_method_additional_note(uri): 530 | uri = URIRef(uri) 531 | for _, _, note in Config.g.triples((uri, SKOS.note, None)): 532 | return note 533 | -------------------------------------------------------------------------------- /skos/collection.py: -------------------------------------------------------------------------------- 1 | from pyldapi.renderer import Renderer 2 | from pyldapi.view import View 3 | from flask import render_template, Response 4 | from rdflib import URIRef, Graph 5 | from rdflib.namespace import SKOS 6 | 7 | 8 | import skos 9 | from skos.common_properties import CommonPropertiesMixin 10 | from config import Config 11 | 12 | 13 | class Collection(CommonPropertiesMixin): 14 | def __init__(self, uri): 15 | CommonPropertiesMixin.__init__(self, uri) 16 | super().__init__(uri) 17 | self.concept_hierarchy = skos.get_concept_hierarchy_collection(uri) 18 | 19 | 20 | class CollectionRenderer(Renderer): 21 | def __init__(self, uri, request): 22 | self.uri = uri 23 | 24 | views = { 25 | 'skos': View( 26 | 'SKOS', 27 | 'Simple Knowledge Organization System (SKOS) is an area of work developing specifications and standards to support the use of knowledge organization systems (KOS) such as thesauri, classification schemes, subject heading lists and taxonomies within the framework of the Semantic Web.', 28 | ['text/html'] + Renderer.RDF_MIMETYPES, 29 | 'text/html', 30 | namespace='http://www.w3.org/2004/02/skos/core#' 31 | ) 32 | } 33 | 34 | super().__init__(request, uri, views, 'skos') 35 | 36 | def _render_skos_rdf(self): 37 | g = Graph() 38 | 39 | for s, p, o in Config.g.triples((URIRef(self.uri), None, None)): 40 | g.add((s, p, o)) 41 | 42 | return Response(g.serialize(format=self.format), mimetype=self.format) 43 | 44 | def render(self): 45 | if not hasattr(self, 'format'): 46 | self.format = 'text/html' 47 | if self.view == 'skos': 48 | if self.format == 'text/html': 49 | cc = skos.Collection(self.uri) 50 | return render_template('skos.html', title=cc.label, c=cc, 51 | skos_class=('http://www.w3.org/2004/02/skos/core#Collection', 'Collection'), 52 | formats=[(format, format.split('/')[-1]) for format in self.views.get('skos').formats]) 53 | elif self.format in Renderer.RDF_MIMETYPES: 54 | return self._render_skos_rdf() 55 | else: 56 | # In theory, this line should never execute because if an invalid format has been entered, the pyldapi 57 | # will default to the default format. In this case, The default format for the default view (skos) is 58 | # text/html. 59 | raise RuntimeError('Invalid format error') 60 | else: 61 | # Let pyldapi handle the rendering of the 'alternates' view. 62 | return super(CollectionRenderer, self).render() -------------------------------------------------------------------------------- /skos/common_properties.py: -------------------------------------------------------------------------------- 1 | import skos 2 | 3 | 4 | class CommonPropertiesMixin: 5 | def __init__(self, uri): 6 | self.uri = uri 7 | self.label = skos.get_label(uri) 8 | self.description = skos.get_description(uri) 9 | self.definition = skos.get_definition(uri) 10 | self.class_types = skos.get_class_types(uri) 11 | self.change_note = skos.get_change_note(uri) 12 | self.alt_labels = skos.get_alt_labels(uri) 13 | self.created = skos.get_created_date(uri) 14 | self.modified = skos.get_modified_date(uri) 15 | self.properties = skos.get_properties(uri) 16 | self.bibliographic_citation = skos.get_bibliographic_citation(uri) 17 | self.is_defined_by = skos.get_is_defined_by(uri) 18 | self.collections = skos.member_of(uri) 19 | self.source = skos.get_dcterms_source(uri) 20 | -------------------------------------------------------------------------------- /skos/concept.py: -------------------------------------------------------------------------------- 1 | from pyldapi.renderer import Renderer 2 | from pyldapi.view import View 3 | from flask import render_template, Response 4 | from rdflib import Graph, URIRef, BNode 5 | 6 | import skos 7 | from skos.common_properties import CommonPropertiesMixin 8 | from skos.schema_org import SchemaOrgMixin, SchemaPersonMixin 9 | from config import Config 10 | 11 | 12 | class Concept(CommonPropertiesMixin, SchemaOrgMixin, SchemaPersonMixin): 13 | def __init__(self, uri): 14 | CommonPropertiesMixin.__init__(self, uri) 15 | SchemaOrgMixin.__init__(self, uri) 16 | SchemaPersonMixin.__init__(self, uri) 17 | self.narrowers = skos.get_narrowers(uri) 18 | self.broaders = skos.get_broaders(uri) 19 | self.top_concept_of = skos.get_top_concept_of(uri) 20 | self.in_scheme = skos.get_in_scheme(uri) 21 | self.close_match = skos.get_close_match(uri) 22 | self.exact_match = skos.get_exact_match(uri) 23 | self.mapping = skos.get_mapping_statement(uri) 24 | 25 | 26 | class ConceptRenderer(Renderer): 27 | def __init__(self, uri, request): 28 | self.uri = uri 29 | 30 | views = { 31 | 'skos': View( 32 | 'SKOS', 33 | 'Simple Knowledge Organization System (SKOS) is an area of work developing specifications and standards to support the use of knowledge organization systems (KOS) such as thesauri, classification schemes, subject heading lists and taxonomies within the framework of the Semantic Web.', 34 | ['text/html'] + Renderer.RDF_MIMETYPES, 35 | 'text/html', 36 | namespace='http://www.w3.org/2004/02/skos/core#' 37 | ) 38 | } 39 | 40 | super().__init__(request, uri, views, 'skos') 41 | 42 | def _render_skos_rdf(self): 43 | g = Graph() 44 | 45 | for subj, pred, obj in Config.g.triples((URIRef(self.uri), None, None)): 46 | g.add((subj, pred, obj)) 47 | if type(obj) == BNode: 48 | for s, p, o in Config.g.triples((obj, None, None)): 49 | g.add((s, p, o)) 50 | 51 | return Response(g.serialize(format=self.format), mimetype=self.format) 52 | 53 | def render(self): 54 | if not hasattr(self, 'format'): 55 | self.format = 'text/html' 56 | if self.view == 'skos': 57 | if self.format == 'text/html': 58 | cc = skos.Concept(self.uri) 59 | return render_template('skos.html', title=cc.label, c=cc, 60 | skos_class=('http://www.w3.org/2004/02/skos/core#Concept', 'Concept'), 61 | formats=[(format, format.split('/')[-1]) for format in self.views.get('skos').formats]) 62 | elif self.format in Renderer.RDF_MIMETYPES: 63 | return self._render_skos_rdf() 64 | else: 65 | # In theory, this line should never execute because if an invalid format has been entered, the pyldapi 66 | # will default to the default format. In this case, The default format for the default view (skos) is 67 | # text/html. 68 | raise RuntimeError('Invalid format error') 69 | else: 70 | # Let pyldapi handle the rendering of the 'alternates' view. 71 | return super(ConceptRenderer, self).render() 72 | -------------------------------------------------------------------------------- /skos/concept_scheme.py: -------------------------------------------------------------------------------- 1 | from pyldapi.renderer import Renderer 2 | from pyldapi.view import View 3 | from flask import render_template, Response 4 | from rdflib import URIRef, Graph 5 | from rdflib.namespace import SKOS 6 | 7 | 8 | import skos 9 | from skos.common_properties import CommonPropertiesMixin 10 | from config import Config 11 | 12 | 13 | class ConceptScheme(CommonPropertiesMixin): 14 | def __init__(self, uri): 15 | CommonPropertiesMixin.__init__(self, uri) 16 | super().__init__(uri) 17 | self.concept_hierarchy = skos.get_concept_hierarchy(uri) 18 | 19 | 20 | class ConceptSchemeRenderer(Renderer): 21 | def __init__(self, uri, request): 22 | self.uri = uri 23 | 24 | views = { 25 | 'skos': View( 26 | 'SKOS', 27 | 'Simple Knowledge Organization System (SKOS) is an area of work developing specifications and standards to support the use of knowledge organization systems (KOS) such as thesauri, classification schemes, subject heading lists and taxonomies within the framework of the Semantic Web.', 28 | ['text/html'] + Renderer.RDF_MIMETYPES, 29 | 'text/html', 30 | namespace='http://www.w3.org/2004/02/skos/core#' 31 | ) 32 | } 33 | 34 | super().__init__(request, uri, views, 'skos') 35 | 36 | def _add_concept_by_narrower(self, g, concept): 37 | for narrower_concept in g.objects(concept, SKOS.narrower): 38 | for subj, pred, obj in Config.g.triples((narrower_concept, None, None)): 39 | g.add((subj, pred, obj)) 40 | self._add_concept_by_narrower(g, narrower_concept) 41 | 42 | def _render_skos_rdf(self): 43 | # TODO: Add this inference algorithm to start-up so that each concept within a concept scheme is tagged with 44 | # the property skos:InScheme. This will reduce the RDF load-time for concept schemes that have lots of 45 | # narrower concepts. 46 | # . 47 | # This method should then be replaced with just getting the concept scheme and concept by skos:inScheme. 48 | g = Graph() 49 | 50 | # Get the concept scheme properties 51 | for subj, pred, obj in Config.g.triples((URIRef(self.uri), None, None)): 52 | g.add((subj, pred, obj)) 53 | 54 | # Get the concepts by skos;inScheme 55 | for concept in Config.g.subjects(SKOS.inScheme, URIRef(self.uri)): 56 | for subj, pred, obj in Config.g.triples((concept, None, None)): 57 | g.add((subj, pred, obj)) 58 | 59 | # Get the concepts by skos:topConceptOf 60 | for concept in Config.g.subjects(SKOS.topConceptOf, URIRef(self.uri)): 61 | self._add_concept_by_narrower(g, concept) 62 | 63 | return Response(g.serialize(format=self.format), mimetype=self.format) 64 | 65 | def render(self): 66 | if not hasattr(self, 'format'): 67 | self.format = 'text/html' 68 | if self.view == 'skos': 69 | if self.format == 'text/html': 70 | cc = skos.ConceptScheme(self.uri) 71 | return render_template('skos.html', title=cc.label, c=cc, 72 | skos_class=('http://www.w3.org/2004/02/skos/core#ConceptScheme', 'Concept Scheme'), 73 | formats=[(format, format.split('/')[-1]) for format in self.views.get('skos').formats]) 74 | elif self.format in Renderer.RDF_MIMETYPES: 75 | return self._render_skos_rdf() 76 | else: 77 | # In theory, this line should never execute because if an invalid format has been entered, the pyldapi 78 | # will default to the default format. In this case, The default format for the default view (skos) is 79 | # text/html. 80 | raise RuntimeError('Invalid format error') 81 | else: 82 | # Let pyldapi handle the rendering of the 'alternates' view. 83 | return super(ConceptSchemeRenderer, self).render() 84 | -------------------------------------------------------------------------------- /skos/method.py: -------------------------------------------------------------------------------- 1 | from pyldapi.renderer import Renderer 2 | from pyldapi.view import View 3 | from flask import render_template, Response 4 | from rdflib import Graph, URIRef, BNode 5 | 6 | import skos 7 | from skos.common_properties import CommonPropertiesMixin 8 | from config import Config 9 | 10 | 11 | class Method(CommonPropertiesMixin): 12 | def __init__(self, uri): 13 | CommonPropertiesMixin.__init__(self, uri) 14 | self.uri = uri 15 | self.purpose = skos.get_method_purpose(uri) 16 | self.scope = skos.get_method_scope(uri) 17 | self.equipment = skos.get_method_equipment(uri) 18 | self.time_required = skos.get_method_time_required(uri) 19 | self.instructions = skos.get_method_instructions(uri) 20 | self.additional_note = skos.get_method_additional_note(uri) 21 | self.parameters = skos.get_parameter_relations(uri) 22 | self.categorical_variables = skos.get_categorical_variables_relations(uri) 23 | 24 | 25 | class MethodRenderer(Renderer): 26 | def __init__(self, uri, request): 27 | self.uri = uri 28 | 29 | views = { 30 | 'method': View( 31 | 'Method', 32 | 'A TERN method.', 33 | ['text/html'] + Renderer.RDF_MIMETYPES, 34 | 'text/html', 35 | namespace='https://w3id.org/tern/ontologies/tern/' 36 | ) 37 | } 38 | 39 | super().__init__(request, uri, views, 'method') 40 | 41 | # TODO: Make a base class and make this a method of the base class. 42 | def render_rdf(self): 43 | g = Graph() 44 | 45 | for subj, pred, obj in Config.g.triples((URIRef(self.uri), None, None)): 46 | g.add((subj, pred, obj)) 47 | if type(obj) == BNode: 48 | for s, p, o in Config.g.triples((obj, None, None)): 49 | g.add((s, p, o)) 50 | 51 | return Response(g.serialize(format=self.format), mimetype=self.format) 52 | 53 | def render(self): 54 | if not hasattr(self, 'format'): 55 | self.format = 'text/html' 56 | if self.view == 'method': 57 | if self.format == 'text/html': 58 | cc = Method(self.uri) 59 | return render_template('method.html', title=cc.label, c=cc, 60 | skos_class=('https://w3id.org/tern/ontologies/tern/Method', 'Method'), 61 | formats=[(format, format.split('/')[-1]) for format in self.views.get('method').formats]) 62 | elif self.format in Renderer.RDF_MIMETYPES: 63 | return self.render_rdf() 64 | else: 65 | # In theory, this line should never execute because if an invalid format has been entered, the pyldapi 66 | # will default to the default format. In this case, The default format for the default view (skos) is 67 | # text/html. 68 | raise RuntimeError('Invalid format error') 69 | else: 70 | # Let pyldapi handle the rendering of the 'alternates' view. 71 | return super(MethodRenderer, self).render() 72 | -------------------------------------------------------------------------------- /skos/register.py: -------------------------------------------------------------------------------- 1 | from flask import render_template 2 | from rdflib import Graph, Namespace, Literal, URIRef 3 | from rdflib.namespace import RDF, DCTERMS, XSD, RDFS, FOAF 4 | 5 | from pyldapi.register_renderer import RegisterRenderer 6 | 7 | from config import Config 8 | 9 | 10 | class Register(RegisterRenderer): 11 | def __init__(self, request, label, comment, items, contained_item_classes, total_items_count, 12 | register_template=None, description=None, 13 | title=None, search_query=None): 14 | if title: 15 | self.title = title 16 | else: 17 | self.title = label 18 | self.description = description 19 | self.search_query = search_query 20 | 21 | super().__init__(request, request.base_url, label, comment, items, contained_item_classes, total_items_count, 22 | register_template=register_template) 23 | 24 | def _generate_dcat_html_metadata(self): 25 | if 'http://www.w3.org/2004/02/skos/core#ConceptScheme' or 'http://www.w3.org/2004/02/skos/core#Collection' in \ 26 | self.contained_item_classes: 27 | # If this register is rendering the view for skos:ConceptSchemes or skos:Collections, 28 | # then inject additional HTML. 29 | 30 | g = Graph() 31 | DCAT = Namespace('http://www.w3.org/ns/dcat#') 32 | 33 | # Catalog 34 | catalog = URIRef(self.request.base_url) 35 | g.add((catalog, RDF.type, DCAT.Catalog)) 36 | g.add((catalog, DCTERMS.title, Literal(Config.title))) 37 | g.add((catalog, RDFS.label, Literal(Config.title))) 38 | g.add((catalog, DCTERMS.description, Literal(Config.description))) 39 | # g.add((catalog, FOAF.landingPage, catalog)) 40 | g.add((catalog, FOAF.homepage, URIRef(self.request.url_root))) 41 | 42 | for item in self.register_items: 43 | # Dataset 44 | g.add((item[0], RDF.type, DCAT.Dataset)) 45 | g.add((item[0], DCTERMS.title, item[1])) 46 | g.add((item[0], RDFS.label, item[1])) 47 | g.add((item[0], DCTERMS.type, URIRef('http://id.loc.gov/vocabulary/marcgt/dic'))) 48 | g.add((item[0], DCAT.landingPage, URIRef('{}id/{}'.format(self.request.host_url, item[0])))) 49 | g.add((catalog, DCAT.dataset, item[0])) 50 | 51 | if item[2][0][1]: 52 | # dcterms:created 53 | g.add((item[0], item[2][0][0], Literal(item[2][0][1], datatype=XSD.date))) 54 | if item[2][1][1]: 55 | # dcterms:modified 56 | g.add((item[0], item[2][1][0], Literal(item[2][1][1], datatype=XSD.date))) 57 | if item[2][2] and item[2][2][1]: 58 | # dcterms:description 59 | g.add((item[0], item[2][2][0], item[2][2][1])) 60 | 61 | # Distribution 62 | subject = URIRef(self.request.host_url + '?_format=text/html&_view=skos') 63 | g.add((subject, RDF.type, DCAT.Distribution)) 64 | g.add((subject, DCAT.downloadURL, subject)) 65 | g.add((subject, DCAT.mediaType, URIRef('https://www.iana.org/assignments/media-types/text/html'))) 66 | g.add((catalog, DCAT.distribution, subject)) 67 | 68 | subject = URIRef(self.request.host_url + '?_format=text/turtle&_view=skos') 69 | g.add((subject, RDF.type, DCAT.Distribution)) 70 | g.add((subject, DCAT.downloadURL, subject)) 71 | g.add((subject, DCAT.mediaType, URIRef('https://www.iana.org/assignments/media-types/text/turtle'))) 72 | g.add((catalog, DCAT.distribution, subject)) 73 | 74 | subject = URIRef(self.request.host_url + '?_format=application/rdf+xml&_view=skos') 75 | g.add((subject, RDF.type, DCAT.Distribution)) 76 | g.add((subject, DCAT.downloadURL, subject)) 77 | g.add((subject, DCAT.mediaType, 78 | URIRef('https://www.iana.org/assignments/media-types/application/rdf+xml'))) 79 | g.add((catalog, DCAT.distribution, subject)) 80 | 81 | subject = URIRef(self.request.host_url + '?_format=application/ld+json&_view=skos') 82 | g.add((subject, RDF.type, DCAT.Distribution)) 83 | g.add((subject, DCAT.downloadURL, subject)) 84 | g.add((subject, DCAT.mediaType, 85 | URIRef('https://www.iana.org/assignments/media-types/application/ld+json'))) 86 | g.add((catalog, DCAT.distribution, subject)) 87 | 88 | subject = URIRef(self.request.host_url + '?_format=text/n3&_view=skos') 89 | g.add((subject, RDF.type, DCAT.Distribution)) 90 | g.add((subject, DCAT.downloadURL, subject)) 91 | g.add((subject, DCAT.mediaType, 92 | URIRef('https://www.iana.org/assignments/media-types/text/n3'))) 93 | g.add((catalog, DCAT.distribution, subject)) 94 | 95 | subject = URIRef(self.request.host_url + '?_format=application/n-triples&_view=skos') 96 | g.add((subject, RDF.type, DCAT.Distribution)) 97 | g.add((subject, DCAT.downloadURL, subject)) 98 | g.add((subject, DCAT.mediaType, 99 | URIRef('https://www.iana.org/assignments/media-types/application/n-triples'))) 100 | g.add((catalog, DCAT.distribution, subject)) 101 | 102 | additional_html = '' 103 | else: 104 | additional_html = None 105 | 106 | return additional_html 107 | 108 | def render(self): 109 | if not hasattr(self, 'format'): 110 | self.format = 'text/html' 111 | if self.view == 'reg' and self.format == 'text/html': 112 | return render_template(self.register_template, 113 | title=self.title, 114 | description=self.description, 115 | class_type=self.contained_item_classes[0], 116 | items=self.register_items, 117 | search_query=self.search_query, 118 | next_page=self.next_page, 119 | prev_page=self.prev_page, 120 | page=self.page, 121 | per_page=self.per_page, 122 | total_items=self.register_total_count, 123 | additional_html=self._generate_dcat_html_metadata()) 124 | if self.view == 'reg' or self.view == 'alternates': 125 | return super(Register, self).render() 126 | -------------------------------------------------------------------------------- /skos/schema_org.py: -------------------------------------------------------------------------------- 1 | import skos 2 | 3 | 4 | class SchemaOrgMixin: 5 | def __init__(self, uri): 6 | self.uri = uri 7 | self.parent_organization = skos.get_schema_org_parent_org(uri) 8 | self.contact_point = skos.get_schema_org_contact_point(uri) 9 | self.members = skos.get_schema_org_members(uri) 10 | self.sub_organizations = skos.get_schema_org_sub_orgs(uri) 11 | 12 | 13 | class SchemaPersonMixin: 14 | def __init__(self, uri): 15 | self.uri = uri 16 | self.family_name = skos.get_schema_org_family_name(uri) 17 | self.given_name = skos.get_schema_org_given_name(uri) 18 | self.honorific_prefix = skos.get_schema_org_honorific_prefix(uri) 19 | self.job_title = skos.get_schema_org_job_title(uri) 20 | self.member_of = skos.get_schema_org_member_of(uri) -------------------------------------------------------------------------------- /static/clipboard.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.4 3 | * https://zenorocha.github.io/clipboard.js 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(n){var o={};function r(t){if(o[t])return o[t].exports;var e=o[t]={i:t,l:!1,exports:{}};return n[t].call(e.exports,e,e.exports,r),e.l=!0,e.exports}return r.m=n,r.c=o,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=0)}([function(t,e,n){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function o(t,e){for(var n=0;n 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/vocview.css: -------------------------------------------------------------------------------- 1 | .card-register { 2 | box-shadow: 0 4px 8px 0 rgba(0,0,0,0.1); 3 | /*transition: 0.3s;*/ 4 | /*background-color: #93c3d4;*/ 5 | background-color: white; 6 | padding: 1px 1em 0.2em 1em; 7 | margin-bottom: 20px; 8 | min-height: 110px !important; 9 | } 10 | 11 | .card-register:hover { 12 | box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2); 13 | /*background-color: #a5d1e0;*/ 14 | } 15 | 16 | .footer { 17 | position: fixed; 18 | left: 0; 19 | bottom: 0; 20 | width: 100%; 21 | background-color: white; 22 | color: #c6c6c6; 23 | height: 25px; 24 | text-align: center; 25 | } 26 | 27 | body { 28 | padding-bottom: 25px; 29 | } 30 | 31 | .display-inline { 32 | display: inline; 33 | } 34 | 35 | .tree-action { 36 | font-size: 12px; 37 | cursor: pointer; 38 | } 39 | 40 | .tree-action:hover { 41 | text-decoration: underline; 42 | } -------------------------------------------------------------------------------- /tasks.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from rdflib import Graph 4 | from owlrl import DeductiveClosure, OWLRL_Semantics 5 | import yaml 6 | from celery import Celery 7 | from celery.utils.log import get_task_logger 8 | from tern_rdf.utils import create_session 9 | 10 | from config import Config 11 | 12 | logger = get_task_logger(__name__) 13 | 14 | app = Celery(__name__) 15 | 16 | broker_url = os.getenv('CELERY_BROKER_URL', 'filesystem://') 17 | broker_dir = os.getenv('CELERY_BROKER_FOLDER', './broker') 18 | 19 | 20 | @app.on_after_configure.connect 21 | def setup_period_tasks(sender, **kwargs): 22 | sender.add_periodic_task(float(Config.store_seconds), fetch_data.s(), name='Fetch data') 23 | 24 | 25 | @app.task 26 | def fetch_data(): 27 | with open(os.path.join(Config.APP_DIR, Config.VOCAB_SOURCES)) as f: 28 | vocabs = yaml.safe_load(f) 29 | g = Graph() 30 | http = create_session() 31 | if vocabs.get('download'): 32 | for vocab in vocabs['download'].values(): 33 | logger.info(f'Fetching from remote URL {vocab["source"]}') 34 | r = http.get(vocab['source']) 35 | r.raise_for_status() 36 | data = r.text 37 | g.parse(data=data, format=vocab['format']) 38 | logger.info(f'Success with code {r.status_code}') 39 | 40 | if Config.reasoner: 41 | DeductiveClosure(OWLRL_Semantics).expand(g) 42 | 43 | path = 'data/data.ttl' 44 | logger.info(f'Serializing to disk at path {path}') 45 | g.serialize(path, format='turtle') 46 | 47 | 48 | app.conf.update({ 49 | 'broker_url': broker_url, 50 | 'broker_transport_options': { 51 | 'data_folder_in': os.path.join(broker_dir, 'out'), 52 | 'data_folder_out': os.path.join(broker_dir, 'out'), 53 | 'data_folder_processed': os.path.join(broker_dir, 'processed') 54 | }, 55 | 'imports': ('tasks',), 56 | 'result_persistent': False, 57 | 'task_serializer': 'json', 58 | 'result_serializer': 'json', 59 | 'accept_content': ['json']}) 60 | -------------------------------------------------------------------------------- /templates/alternates.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Alternates View

5 |

Instance {{ name }}

6 |

Default view: {{ default_view_token }}

7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% for token, vals in views.items() %} 18 | 19 | 20 | 21 | 26 | 31 | 32 | {% if vals['namespace'] is not none %} 33 | 34 | {% endif %} 35 | 36 | {% endfor %} 37 |
View tokenNameFormatsLanguagesDescriptionNamespace
{{ token }}{{ vals['label'] }} 22 | {% for f in vals['formats'] %} 23 | {{ f }}
24 | {% endfor %} 25 |
27 | {% for l in vals['languages'] %} 28 | {{ l }}
29 | {% endfor %} 30 |
{{ vals['comment'] }}{{ vals['namespace'] }}
38 |
39 | {% endblock %} -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{ config.title }} 11 | 12 | 13 | 14 |
15 | 26 |
27 | 28 | {% block content %}{% endblock %} 29 |
30 | 31 |
32 |
33 | VocView {{ config._version }} 34 |
35 |
36 |
37 |
38 | 39 |
40 |
41 | 42 | 43 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 67 | 70 | -------------------------------------------------------------------------------- /templates/concept-scheme-change-note.html: -------------------------------------------------------------------------------- 1 | {% if cc.change_note %} 2 | {{ popover_uri('Change note', 'URI', 'http://www.w3.org/2004/02/skos/core#changeNote', 'h6') }} 3 | 4 |

5 | {{ cc.change_note }} 6 |

7 | {% endif %} -------------------------------------------------------------------------------- /templates/elements/alt_label.html: -------------------------------------------------------------------------------- 1 | {% if c.alt_labels %} 2 | {{ popover_uri('Also known as', 'URI', 'http://www.w3.org/2004/02/skos/core#altLabel') }} 3 | 4 | 5 | {% for lab in c.alt_labels %} 6 | {{ lab }}{% if loop.index != c.alt_labels|length %},{% endif %} 7 | {% endfor %} 8 | 9 | {% endif %} -------------------------------------------------------------------------------- /templates/elements/date.html: -------------------------------------------------------------------------------- 1 | {% from "macros/popover.html" import popover_uri with context %} 2 | 3 |

4 | {{ popover_uri('Date created:', 'URI', 'http://purl.org/dc/terms/created') }} 5 | {% if c.created %} 6 | {{ c.created }} 7 | {% else %} 8 | not supplied 9 | {% endif %} 10 | 11 | 12 |
13 | 14 | {{ popover_uri('Date modified:', 'URI', 'http://purl.org/dc/terms/modified') }} 15 | {% if c.modified %} 16 | {{ c.modified }} 17 | {% else %} 18 | not supplied 19 | {% endif %} 20 | 21 |

-------------------------------------------------------------------------------- /templates/elements/uri.html: -------------------------------------------------------------------------------- 1 |

2 | URI: {{ c.uri }} 3 | 6 |

-------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% from "macros/popover.html" import popover_uri with context %} 4 | {% from "macros/render.html" import render_instance_uri with context %} 5 | 6 | {% block content %} 7 | 8 | {% include "info.html" %} 9 | 10 |

Registers

11 | 15 | 16 |

Download

17 |

The entire data in this system is available for download in a serialised RDF format.

18 | 25 |

The data can also be viewed at vocabulary or concept level as RDF.

26 | {% endblock %} -------------------------------------------------------------------------------- /templates/info.html: -------------------------------------------------------------------------------- 1 |

Vocabulary Viewer

2 | 3 |

A Linked Data API for vocabularies encoded in SKOS.

-------------------------------------------------------------------------------- /templates/macros/alt_labels.html: -------------------------------------------------------------------------------- 1 | {% macro render_alt_labels(alt_labels) %} 2 | {% if alt_labels %} 3 | {{ popover_uri('Also known as', 'URI', 'http://www.w3.org/2004/02/skos/core#altLabel') }} 4 | 5 | {% if alt_labels %} 6 | {% for lab in alt_labels %} 7 | {{ lab }}{% if loop.index != alt_labels|length %},{% endif %} 8 | {% endfor %} 9 | {% endif %} 10 | {% endif %} 11 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/bibliographic_citation.html: -------------------------------------------------------------------------------- 1 | {% macro render_bib_citation(bib_cit) %} 2 | {% if bib_cit %} 3 | {{ popover_uri('Bibliographic citation', 'URI', 'http://purl.org/dc/terms/bibliographicCitation', 'h5') }} 4 | 5 | {{ render(bib_cit) }} 6 | {% endif %} 7 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/broaders.html: -------------------------------------------------------------------------------- 1 | {% macro render_broaders(broaders) %} 2 | {% if broaders %} 3 | 4 | {{ popover_uri('Broaders', 'URI', 'http://www.w3.org/2004/02/skos/core#broader', 'h5') }} 5 | 6 |
    7 | {% for broader in broaders %} 8 | 9 |
  • {{ render_instance_uri(broader[0], broader[1]) }}
  • 10 | 11 | {% endfor %} 12 |
13 | 14 | {% endif %} 15 | 16 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/change-note.html: -------------------------------------------------------------------------------- 1 | {% macro render_change_note(change_note) %} 2 | {% if change_note %} 3 | {{ popover_uri('Change note', 'URI', 'http://www.w3.org/2004/02/skos/core#changeNote', 'h5') }} 4 | 5 | {{ render(change_note) }} 6 | {% endif %} 7 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/close_match.html: -------------------------------------------------------------------------------- 1 | {% macro render_close_match(close_match) %} 2 | {% if close_match %} 3 | {{ popover_uri('Close match', 'URI', 'http://www.w3.org/2004/02/skos/core#closeMatch', 'h5') }} 4 | 5 |
    6 | {% for cm in close_match %} 7 |
  • {{ render(cm) }}
  • 8 | {% endfor %} 9 |
10 | 11 | {% endif %} 12 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/concept_hierarchy.html: -------------------------------------------------------------------------------- 1 | {% macro render_concept_hierarchy(hierarchy) %} 2 | 41 | 42 | {% if hierarchy %} 43 |
Hierarchy
expand all 44 | 45 | {{ h.render_concept_tree(hierarchy)|safe }} 46 | {% endif %} 47 | 48 | 59 | 60 | 81 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/date-metadata.html: -------------------------------------------------------------------------------- 1 | {% macro date_created(created) %} 2 | {{ popover_uri('Date created:', 'URI', 'http://purl.org/dc/terms/created') }} 3 | {% if created %} 4 | {{ created }} 5 | {% else %} 6 | not supplied 7 | {% endif %} 8 | 9 | {% endmacro %} 10 | 11 | {% macro date_modified(modified) %} 12 | {{ popover_uri('Date modified:', 'URI', 'http://purl.org/dc/terms/modified') }} 13 | {% if modified %} 14 | {{ modified }} 15 | {% else %} 16 | not supplied 17 | {% endif %} 18 | 19 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/definition.html: -------------------------------------------------------------------------------- 1 | {% macro render_definition(definition) %} 2 | 3 | {% if definition %} 4 | {{ popover_uri('Definition', 'URI', 'http://www.w3.org/2004/02/skos/core#definition', 'h5') }} 5 | 6 |

7 | {{ render(definition) }} 8 |

9 | {% endif %} 10 | 11 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/description.html: -------------------------------------------------------------------------------- 1 | {# 2 | Input: 3 | description - tuple(uri, description_string) 4 | #} 5 | 6 | {% macro render_description(description) %} 7 | {% if description %} 8 | {{ popover_uri('Description', 'URI', 'http://purl.org/dc/terms/description', 'h5') }} 9 | 10 | {{ render(description[1]) }} 11 | {% endif %} 12 | 13 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/exact_match.html: -------------------------------------------------------------------------------- 1 | {% macro render_exact_match(exact_match) %} 2 | 3 | {% if exact_match %} 4 | {{ popover_uri('Exact match', 'URI', 'http://www.w3.org/2004/02/skos/core#exactMatch', 'h5') }} 5 | 6 |
    7 | {% for em in exact_match %} 8 |
  • {{ render(em) }}
  • 9 | {% endfor %} 10 |
11 | 12 | {% endif %} 13 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/header.html: -------------------------------------------------------------------------------- 1 | {% macro render_header(class_types, skos_type) %} 2 |
{{ popover_uri(skos_type[1], 'URI', skos_type[0]) }} | 3 | {% for type in class_types %} 4 | {{ popover_uri(type, 'URI', type) }}| 5 | {% endfor %} 6 | 7 |
8 | {% if formats %} 9 | {% for format in formats %} 10 | {{ format[1] }} 11 | {% endfor %}| 12 | {% endif %} 13 | Alternates view 14 |
15 |
16 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/in_scheme.html: -------------------------------------------------------------------------------- 1 | {% macro render_in_scheme(schemes) %} 2 | {% if schemes %} 3 | {{ popover_uri('In concept scheme', 'URI', 'http://www.w3.org/2004/02/skos/core#inScheme', 'h5') }} 4 | 5 |

6 | {% for scheme in schemes %} 7 | {{ render_instance_uri(scheme[0], scheme[1]) }} 8 | {% endfor %} 9 |

10 | {% endif %} 11 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/label.html: -------------------------------------------------------------------------------- 1 | {% macro render_label(uri, label) %} 2 |

{{ label }}

3 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/mapping_statement.html: -------------------------------------------------------------------------------- 1 | {% macro render_mapping_statement(statement) %} 2 | 3 |
Mapping to upper vocabulary
4 | 11 | 12 | 13 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/members.html: -------------------------------------------------------------------------------- 1 | {% from "macros/popover.html" import popover_uri with context %} 2 | {% from "macros/render.html" import render, render_instance_uri with context %} 3 | 4 | {% macro render_members(members) %} 5 | {% if members %} 6 | 7 | {{ popover_uri('Members', 'URI', 'http://www.w3.org/2004/02/skos/core#member', 'h5') }} 8 | 9 |
    10 | {% for member in members %} 11 | 12 |
  • {{ render_instance_uri(member[0], member[1]) }}
  • 13 | 14 | {% endfor %} 15 |
16 | 17 | {% endif %} 18 | 19 | {% endmacro %} 20 | 21 | 22 | {% macro render_member_of(collections) %} 23 | {% if collections %} 24 | {{ popover_uri('Member of', 'URI', 'http://www.w3.org/2004/02/skos/core#memberOf', 'h5') }} 25 |
    26 | {% for collection in collections %} 27 | 28 |
  • {{ render_instance_uri(collection[0], collection[1]) }}
  • 29 | 30 | {% endfor %} 31 |
32 | {% endif %} 33 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/narrowers.html: -------------------------------------------------------------------------------- 1 | {% macro render_narrowers(narrowers) %} 2 | {% if narrowers %} 3 | 4 | {{ popover_uri('Narrowers', 'URI', 'http://www.w3.org/2004/02/skos/core#narrower', 'h5') }} 5 | 6 |
    7 | {% for narrower in narrowers %} 8 | 9 |
  • {{ render_instance_uri(narrower[0], narrower[1]) }}
  • 10 | 11 | {% endfor %} 12 |
13 | 14 | {% endif %} 15 | 16 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/popover.html: -------------------------------------------------------------------------------- 1 | {% macro popover_uri(label, title, uri=None, heading_type=None) %} 2 | {% if heading_type %} 3 | <{{ heading_type }} class="card-title">{{ label }} 4 | {% elif not uri %} 5 | {{ h.uri_label(label) }} 6 | {% else %} 7 | {{ h.uri_label(label) }} 8 | {% endif %} 9 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/properties.html: -------------------------------------------------------------------------------- 1 | {% from "macros/render.html" import render, render_instance_uri with context %} 2 | 3 | {% macro render_properties(properties) %} 4 | {% if properties %} 5 | {% for property in properties %} 6 | 7 | {% if property[0][1] %} 8 | {{ popover_uri(h.uri_label(property[0][1]), 'URI', property[0][0], 'h5') }} 9 | {% else %} 10 | {{ popover_uri(h.uri_label(property[0][0]), 'URI', property[0][0], 'h5') }} 11 | {% endif %} 12 | 13 | {% if property[2] %} 14 | {{ render_instance_uri(property[1], property[2]) }} 15 | {% else %} 16 | {{ render(property[1]) }} 17 | {% endif %} 18 | 19 | {% endfor %} 20 | {% endif %} 21 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/rdfs_is_defined_by.html: -------------------------------------------------------------------------------- 1 | {% macro render_is_defined_by(is_def) %} 2 | {% if is_def %} 3 | {{ popover_uri('Defined by', 'URI', 'http://www.w3.org/2000/01/rdf-schema#isDefinedBy', 'h5') }} 4 | 5 | {{ render(is_def) }} 6 | {% endif %} 7 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/render.html: -------------------------------------------------------------------------------- 1 | {% macro render(text) %} 2 | {{ h.render(text)|safe }} 3 | {% endmacro %} 4 | 5 | {% macro render_instance_uri(uri, label) %} 6 | {{ h.render_instance_uri(uri, label)|safe }} 7 | {% endmacro %} 8 | 9 | {% macro render_property(value, popover_label, popover_uri_value, popover_heading_type=None) %} 10 | {% if value %} 11 | {{ popover_uri(popover_label, 'URI', popover_uri_value, 'h5') }} 12 | 13 |

14 | {{ render(value) }} 15 |

16 | {% endif %} 17 | {% endmacro %} 18 | 19 | {% macro render_property_no_value(popover_label, popover_uri_value, popover_heading_type=None) %} 20 | {{ popover_uri(popover_label, 'URI', popover_uri_value, 'h5') }} 21 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/schemaorg_contact_point.html: -------------------------------------------------------------------------------- 1 | {% macro render_contact_point(cp) %} 2 | {% if cp %} 3 | {{ popover_uri('Contact point', 'URI', 'https://schema.org/ContactPoint', 'h5') }} 4 | 5 |

6 | {{ render_instance_uri(cp[0], cp[1]) }} 7 |

8 | {% endif %} 9 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/schemaorg_family_name.html: -------------------------------------------------------------------------------- 1 | {% macro render_family_name(fn) %} 2 | {% if fn %} 3 | {{ popover_uri('Family name', 'URI', 'https://schema.org/familyName', 'h5') }} 4 | 5 |

6 | {{ render(fn) }} 7 |

8 | {% endif %} 9 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/schemaorg_given_name.html: -------------------------------------------------------------------------------- 1 | {% macro render_given_name(gn) %} 2 | {% if gn %} 3 | {{ popover_uri('Given name', 'URI', 'https://schema.org/givenName', 'h5') }} 4 | 5 |

6 | {{ render(gn) }} 7 |

8 | {% endif %} 9 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/schemaorg_honorific_prefix.html: -------------------------------------------------------------------------------- 1 | {% macro render_honorific_prefix(hp) %} 2 | {% if hp %} 3 | {{ popover_uri('Honorific prefix', 'URI', 'https://schema.org/honorificPrefix', 'h5') }} 4 | 5 |

6 | {{ render(hp) }} 7 |

8 | {% endif %} 9 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/schemaorg_job_title.html: -------------------------------------------------------------------------------- 1 | {% macro render_job_title(jt) %} 2 | {% if jt %} 3 | {{ popover_uri('Job title', 'URI', 'https://schema.org/jobTitle', 'h5') }} 4 | 5 |

6 | {{ render(jt) }} 7 |

8 | {% endif %} 9 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/schemaorg_member_of.html: -------------------------------------------------------------------------------- 1 | {% macro render_member_of(org) %} 2 | {% if org %} 3 | {{ popover_uri('Member of', 'URI', 'https://schema.org/memberOf', 'h5') }} 4 | 5 |

6 | {{ render_instance_uri(org[0], org[1]) }} 7 |

8 | {% endif %} 9 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/schemaorg_members.html: -------------------------------------------------------------------------------- 1 | {% macro render_org_members(members) %} 2 | {% if members %} 3 | {{ popover_uri('Organization members', 'URI', 'https://schema.org/member', 'h5') }} 4 | 5 |
    6 | {% for member in members %} 7 | 8 |
  • {{ render_instance_uri(member[0], member[1]) }}
  • 9 | 10 | {% endfor %} 11 |
12 | {% endif %} 13 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/schemaorg_parent_org.html: -------------------------------------------------------------------------------- 1 | {% macro render_parent_org(org) %} 2 | {% if org %} 3 | {{ popover_uri('Parent Organization', 'URI', 'https://schema.org/parentOrganization', 'h5') }} 4 | 5 |

6 | {{ render_instance_uri(org[0], org[1]) }} 7 |

8 | {% endif %} 9 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/schemaorg_sub_orgs.html: -------------------------------------------------------------------------------- 1 | {% macro render_sub_orgs(orgs) %} 2 | {% if orgs %} 3 | {{ popover_uri('Sub organizations', 'URI', 'https://schema.org/subOrganization', 'h5') }} 4 | 5 |
    6 | {% for org in orgs %} 7 | 8 |
  • {{ render_instance_uri(org[0], org[1]) }}
  • 9 | 10 | {% endfor %} 11 |
12 | {% endif %} 13 | {% endmacro %} -------------------------------------------------------------------------------- /templates/macros/top_concept_of.html: -------------------------------------------------------------------------------- 1 | {% macro render_top_concept_of(tcos) %} 2 | {% if tcos %} 3 | 4 | {{ popover_uri('Top concept of', 'URI', 'http://www.w3.org/2004/02/skos/core#topConceptOf', 'h5') }} 5 | 6 |
    7 | {% for tco in tcos%} 8 | 9 |
  • {{ render_instance_uri(tco[0], tco[1]) }}
  • 10 | 11 | {% endfor %} 12 |
13 | 14 | {% endif %} 15 | 16 | {% endmacro %} -------------------------------------------------------------------------------- /templates/method.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% from "macros/popover.html" import popover_uri with context %} 4 | {% from "macros/header.html" import render_header with context %} 5 | {% from "macros/label.html" import render_label with context %} 6 | {% from "macros/alt_labels.html" import render_alt_labels with context %} 7 | {% from "macros/members.html" import render_members, render_member_of with context %} 8 | {% from "macros/render.html" import render_property, render_property_no_value, render_instance_uri with context %} 9 | 10 | {% block content %} 11 | 12 | {{ render_header(c.class_types, skos_class) }} 13 | 14 | {{ render_label(c.uri, c.label) }} 15 | 16 | {% include "elements/uri.html" %} 17 | 18 | {% include "elements/alt_label.html" %} 19 | 20 | {% include "elements/date.html" %} 21 | 22 | {{ render_member_of(c.collections) }} 23 | 24 | {% if c.purpose %} 25 | {{ render_property(c.purpose, 'Purpose', 'https://w3id.org/tern/ontologies/tern/purpose', 'h5') }} 26 | {% else %} 27 | {{ render_property('not supplied', 'Purpose', 'https://w3id.org/tern/ontologies/tern/purpose', 'h5') }} 28 | {% endif %} 29 | 30 | {% if c.scope %} 31 | {{ render_property(c.scope, 'Scope', 'https://w3id.org/tern/ontologies/tern/scope', 'h5') }} 32 | {% else %} 33 | {{ render_property('not supplied', 'Scope', 'https://w3id.org/tern/ontologies/tern/scope', 'h5') }} 34 | {% endif %} 35 | 36 | {% if c.definition %} 37 | {{ render_property(c.definition, 'Definition', 'http://www.w3.org/2004/02/skos/core#definition', 'h5') }} 38 | {% else %} 39 | {{ render_property('not supplied', 'Definition', 'http://www.w3.org/2004/02/skos/core#definition', 'h5') }} 40 | {% endif %} 41 | 42 | {% if c.equipment %} 43 | {% if h.is_list(c.equipment) %} 44 | {{ render_property_no_value('Equipment', 'https://w3id.org/tern/ontologies/tern/equipment', 'h5') }} 45 |
    46 | {% for equipment in c.equipment %} 47 |
  • {{ render_instance_uri(equipment[0], equipment[1]) }}
  • 48 | {% endfor %} 49 |
50 | {% else %} 51 | {{ render_property(c.equipment, 'Equipment', 'https://w3id.org/tern/ontologies/tern/equipment', 'h5') }} 52 | {% endif %} 53 | {% else %} 54 | {{ render_property('not supplied', 'Equipment', 'https://w3id.org/tern/ontologies/tern/equipment', 'h5') }} 55 | {% endif %} 56 | 57 | {% if c.time_required %} 58 | {{ render_property(c.time_required, 'Time required', 'http://schema.org/timeRequired', 'h5') }} 59 | {% else %} 60 | {{ render_property('not supplied', 'Time required', 'http://schema.org/timeRequired', 'h5') }} 61 | {% endif %} 62 | 63 | {% if c.instructions %} 64 | {{ render_property(c.instructions, 'Instructions', 'https://w3id.org/tern/ontologies/tern/instructions', 'h5') }} 65 | {% else %} 66 | {{ render_property('not supplied', 'Instructions', 'https://w3id.org/tern/ontologies/tern/instructions', 'h5') }} 67 | {% endif %} 68 | 69 | {% if c.additional_note %} 70 | {{ render_property(c.additional_note, 'Additional notes', 'http://www.w3.org/2004/02/skos/core#note', 'h5') }} 71 | {% else %} 72 | {{ render_property('not supplied', 'Additional notes', 'http://www.w3.org/2004/02/skos/core#note', 'h5') }} 73 | {% endif %} 74 | 75 | {% if c.source %} 76 | {{ render_property(c.source, 'Source', 'http://purl.org/dc/terms/source', 'h5') }} 77 | {% else %} 78 | {{ render_property('not supplied', 'Source', 'http://purl.org/dc/terms/source', 'h5') }} 79 | {% endif %} 80 | 81 | {% if c.parameters %} 82 | {{ render_property_no_value('Parameter', 'https://w3id.org/tern/ontologies/tern/hasParameter', 'h5') }} 83 | 84 |
    85 | {% for parameter in c.parameters %} 86 |
  • {{ render_instance_uri(parameter[0], parameter[1]) }}
  • 87 | {% endfor %} 88 |
89 | {% endif %} 90 | 91 | {% if c.categorical_variables %} 92 | {{ render_property_no_value('Categorical variable collection', 'https://w3id.org/tern/ontologies/tern/hasCategoricalVariableCollection', 'h5') }} 93 | 94 |
    95 | {% for cv in c.categorical_variables %} 96 |
  • {{ render_instance_uri(cv[0], cv[1]) }}
  • 97 | {% endfor %} 98 |
99 | {% endif %} 100 | 101 | {% endblock %} -------------------------------------------------------------------------------- /templates/register.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% from "macros/popover.html" import popover_uri with context %} 4 | {% from "macros/render.html" import render_instance_uri with context %} 5 | 6 | {% block content %} 7 | {# 8 | {{ popover_uri(title, 'URI', class_type, 'h2') }} 9 | #} 10 | {{ additional_html | safe }} 11 |

{{ title }}

12 | 13 | {% if description %} 14 |

{{ description }}

15 | {% endif %} 16 | 17 |

Alternate views of this register are available here

18 | 19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 | 27 | {% if search_query %} 28 |

Results for "{{ search_query }}"

29 | {% endif %} 30 | 31 | {% if items|length == 0 %} 32 | No results found. 33 | {% endif %} 34 | 35 | {% if items %} 36 |

Displaying {{ ((page - 1) * per_page) + 1 }} to {% if page * per_page < total_items %}{{ page * per_page }}{% else %}{{ total_items }}{% endif %} of {{ total_items }} items.

37 | 38 | 47 | 48 | {% for cc in items %} 49 |
50 |
51 |
{{ render_instance_uri(cc[0], cc[1]) }}
52 |
53 | {% for prop in cc[2] %} 54 | {% if prop[0] %} 55 |
{{ popover_uri(h.uri_label(prop[0]), 'URI', prop[0]) }}: 56 | {% if prop[1] %} 57 | {% if h.is_list(prop[1]) %} 58 | {{ render_instance_uri(prop[1][0][0], prop[1][0][1]) }} 59 | {% else %} 60 | {{ h.render_property_restricted(prop[1]) }} 61 | {% endif %} 62 | {% else %} 63 | not supplied 64 | {% endif %} 65 |
66 | {% endif %} 67 | {% endfor %} 68 |
69 | 70 |
71 | {% endfor %} 72 | 73 | 82 | 83 | {% endif %} 84 | 85 | {% endblock %} 86 | -------------------------------------------------------------------------------- /templates/skos.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% from "macros/render.html" import render, render_instance_uri with context %} 4 | {% from "macros/popover.html" import popover_uri with context %} 5 | {% from "macros/date-metadata.html" import date_created, date_modified with context %} 6 | {% from "macros/header.html" import render_header with context %} 7 | {% from "macros/description.html" import render_description with context %} 8 | {% from "macros/change-note.html" import render_change_note with context %} 9 | {% from "macros/label.html" import render_label with context %} 10 | {% from "macros/definition.html" import render_definition with context %} 11 | {% from "macros/top_concept_of.html" import render_top_concept_of with context %} 12 | {% from "macros/broaders.html" import render_broaders with context %} 13 | {% from "macros/narrowers.html" import render_narrowers with context %} 14 | {% from "macros/alt_labels.html" import render_alt_labels with context %} 15 | {% from "macros/properties.html" import render_properties with context %} 16 | {% from "macros/in_scheme.html" import render_in_scheme with context %} 17 | {% from "macros/bibliographic_citation.html" import render_bib_citation with context %} 18 | {% from "macros/concept_hierarchy.html" import render_concept_hierarchy with context %} 19 | {% from "macros/rdfs_is_defined_by.html" import render_is_defined_by with context %} 20 | {% from "macros/close_match.html" import render_close_match with context %} 21 | {% from "macros/exact_match.html" import render_exact_match with context %} 22 | {% from "macros/schemaorg_parent_org.html" import render_parent_org with context %} 23 | {% from "macros/schemaorg_contact_point.html" import render_contact_point with context %} 24 | {% from "macros/schemaorg_members.html" import render_org_members with context %} 25 | {% from "macros/schemaorg_sub_orgs.html" import render_sub_orgs with context %} 26 | {% from "macros/schemaorg_family_name.html" import render_family_name with context %} 27 | {% from "macros/schemaorg_given_name.html" import render_given_name with context %} 28 | {% from "macros/schemaorg_honorific_prefix.html" import render_honorific_prefix with context %} 29 | {% from "macros/schemaorg_job_title.html" import render_job_title with context %} 30 | {% from "macros/schemaorg_member_of.html" import render_member_of with context %} 31 | {% from "macros/members.html" import render_members, render_member_of with context %} 32 | {% from "macros/mapping_statement.html" import render_mapping_statement with context %} 33 | 34 | {% block content %} 35 | 36 | 37 |
38 | 39 | {{ render_header(c.class_types, skos_class) }} 40 | 41 |
42 | 43 | {{ render_label(c.uri, c.label) }} 44 | 45 | {% include "elements/uri.html" %} 46 | 47 | {% include "elements/alt_label.html" %} 48 | 49 | {% include "elements/date.html" %} 50 | 51 | {{ render_member_of(c.collections) }} 52 | 53 | {# Schema.org Person #} 54 | {{ render_family_name(c.family_name) }} 55 | {{ render_given_name(c.given_name) }} 56 | {{ render_honorific_prefix(c.honorific_prefix) }} 57 | {{ render_job_title(c.job_title) }} 58 | {{ render_member_of(c.member_of) }} 59 | {# end Schema.org Person #} 60 | 61 | {# Schema.org Org #} 62 | {{ render_parent_org(c.parent_organization) }} 63 | 64 | {{ render_contact_point(c.contact_point) }} 65 | 66 | {{ render_org_members(c.members) }} 67 | 68 | {{ render_sub_orgs(c.sub_organizations) }} 69 | {# end Schema.org Org #} 70 | 71 | {{ render_in_scheme(c.in_scheme) }} 72 | 73 | {{ render_description(c.description) }} 74 | 75 | {{ render_definition(c.definition) }} 76 | 77 | {{ render_members(c.skos_members) }} 78 | 79 | {{ render_is_defined_by(c.is_defined_by) }} 80 | 81 | {{ render_top_concept_of(c.top_concept_of) }} 82 | 83 | {% if c.mapping %} 84 | {{ render_mapping_statement(c.mapping) }} 85 | {% endif %} 86 | 87 | {{ render_broaders(c.broaders) }} 88 | 89 | {{ render_narrowers(c.narrowers) }} 90 | 91 | {{ render_exact_match(c.exact_match) }} 92 | 93 | {{ render_close_match(c.close_match) }} 94 | 95 | {% if c.top_concepts %} 96 | {{ popover_uri('Top concepts', 'URI', 'http://www.w3.org/2004/02/skos/core#hasTopConcept', 'h5') }} 97 |
    98 | {% for tc in c.top_concepts %} 99 |
  • {{ render_instance_uri(tc[0], tc[1]) }}
  • 100 | {% endfor %} 101 |
102 | {% endif %} 103 | 104 | {{ render_concept_hierarchy(c.concept_hierarchy) }} 105 | 106 | {{ render_change_note(c.change_note) }} 107 | 108 | {{ render_properties(c.properties) }} 109 | 110 | {{ render_bib_citation(c.bibliographic_citation) }} 111 | 112 |
113 |
114 | 115 | {% endblock %} -------------------------------------------------------------------------------- /triplestore.py: -------------------------------------------------------------------------------- 1 | # from rdflib import ConjunctiveGraph, Graph, URIRef, Literal 2 | # from rdflib.namespace import DCTERMS, XSD 3 | # import yaml 4 | # from owlrl import DeductiveClosure, OWLRL_Semantics 5 | # import requests 6 | # 7 | # from config import Config 8 | # 9 | # import os 10 | # from datetime import datetime, timedelta 11 | # import logging 12 | # import time 13 | # 14 | # 15 | # class InvalidTriplestoreType(Exception): 16 | # pass 17 | # 18 | # 19 | # class Triplestore: 20 | # THIS_GRAPH = URIRef('http://www.this-graph.com/123456789/') 21 | # loading = False 22 | # 23 | # @staticmethod 24 | # def get_db(triplestore_type): 25 | # if triplestore_type == 'memory': 26 | # if not hasattr(Config, 'g'): 27 | # # g = Triplestore._create_db() 28 | # g = Graph() 29 | # else: 30 | # g = Config.g 31 | # # for date in g.objects(Triplestore.THIS_GRAPH, DCTERMS.created): 32 | # # now = datetime.now() 33 | # # now -= timedelta(hours=Config.store_hours, minutes=Config.store_minutes) 34 | # # if now > date.toPython(): 35 | # # g = Triplestore._create_db(g) 36 | # else: 37 | # raise InvalidTriplestoreType( 38 | # 'Expected one of: [memory]. Instead got {}'.format(triplestore_type)) 39 | # return g 40 | # 41 | # @staticmethod 42 | # def _create_db(old_g: Graph = None): 43 | # start_time = time.time() 44 | # 45 | # g = ConjunctiveGraph() 46 | # 47 | # if not Triplestore.loading: 48 | # logging.info('Creating new graph') 49 | # Triplestore.loading = True 50 | # Triplestore._add_triples(g) 51 | # 52 | # # Add time of creation of new Graph 53 | # g.add((Triplestore.THIS_GRAPH, DCTERMS.created, Literal(datetime.now(), datatype=XSD.dateTime))) 54 | # 55 | # logging.info(f'time taken: {(time.time() - start_time):.2f} seconds') 56 | # Triplestore.loading = False 57 | # else: 58 | # if old_g: 59 | # return old_g 60 | # 61 | # return g 62 | # 63 | # @staticmethod 64 | # def _add_triples(g): 65 | # # Read in RDF from online sources to the Graph. 66 | # logging.info('Pulling RDF triples from remote resources.') 67 | # with open(os.path.join(Config.APP_DIR, Config.VOCAB_SOURCES)) as f: 68 | # vocabs = yaml.safe_load(f) 69 | # 70 | # # Online resources 71 | # if vocabs.get('download'): 72 | # for vocab in vocabs['download'].values(): 73 | # r = requests.get(vocab['source']) 74 | # data = r.content.decode('utf-8') 75 | # g.parse(format=vocab['format'], data=data) 76 | # 77 | # # Local resources 78 | # if vocabs.get('local'): 79 | # for vocab in vocabs['local'].values(): 80 | # g: Graph 81 | # g.parse(os.path.join(Config.APP_DIR, 'local_vocabs', vocab['source']), format=vocab['format']) 82 | # 83 | # # SPARQL resources 84 | # pass 85 | # 86 | # # RVA 87 | # resource_endpoint = vocabs['rva']['resource_endpoint'] 88 | # download_endpoint = vocabs['rva']['download_endpoint'] 89 | # extension = vocabs['rva']['extension'] 90 | # format = vocabs['rva']['format'] 91 | # for id in vocabs['rva']['ids']: 92 | # r = requests.get(resource_endpoint.format(id), headers={'accept': 'application/json'}) 93 | # try: 94 | # response = r.json() 95 | # versions = response['version'] 96 | # download_id = None 97 | # for version in versions: 98 | # if version['status'] == 'current': 99 | # access_points = version['access-point'] 100 | # for access_point in access_points: 101 | # if access_point.get('ap-sesame-download'): 102 | # download_id = access_point['id'] 103 | # 104 | # if download_id is None: 105 | # # Sesame endpoing not found, go for the Turtle file 106 | # for access_point in access_points: 107 | # if access_point.get('ap-file'): 108 | # g.parse(access_point['ap-file']['url'], format='turtle') 109 | # 110 | # if download_id: 111 | # r = requests.get(download_endpoint.format(download_id), params={'format': extension}) 112 | # g.parse(data=r.content.decode('utf-8'), format=format) 113 | # except Exception as e: 114 | # raise Exception('Something wrong with the response of RVA ID {}. Error: {}'.format(id, e)) 115 | # 116 | # # Expand graph using a rule-based inferencer. 117 | # if Config.reasoner: 118 | # DeductiveClosure(OWLRL_Semantics).expand(g) 119 | -------------------------------------------------------------------------------- /vocabs.yaml: -------------------------------------------------------------------------------- 1 | download: 2 | skos_rules: 3 | source: https://bitbucket.org/terndatateam/knowledge-graph-data/raw/HEAD/onto_rules/skos_rules.ttl 4 | format: turtle 5 | # ausplots: 6 | # source: https://graphdb.tern.org.au/repositories/ausplots_vocabs_core/statements?context= 7 | # format: turtle 8 | dawe: 9 | source: https://graphdb.tern.org.au/repositories/dawe_vocabs_core/statements?context= 10 | format: turtle 11 | # tern: 12 | # source: https://bitbucket.org/terndatateam/tern-cv/raw/HEAD/data.ttl 13 | # format: turtle 14 | # tern_vocabs: 15 | # source: https://graphdb.tern.org.au/repositories/tern_vocabs_core/statements?context= 16 | # format: turtle 17 | # tern_vocabs_ozflux: 18 | # source: https://graphdb.tern.org.au/repositories/tern_vocabs_core/statements?context= 19 | # format: turtle 20 | # bioimages_vocabs: 21 | # source: https://graphdb.tern.org.au/repositories/bioimages_vocabs_core/statements?context= 22 | # format: turtle 23 | rva: 24 | resource_endpoint: 'https://vocabs.ands.org.au/registry/api/resource/vocabularies/{}?includeVersions=true&includeAccessPoints=true&includeRelatedEntitiesAndVocabularies=false' 25 | download_endpoint: 'https://vocabs.ands.org.au/registry/api/resource/downloads/{}' 26 | extension: ttl # Use Turtle as it has excellent compression compared to other RDF serialisations 27 | format: turtle 28 | ids: [ 29 | #264, # TERN Orgs and People list 30 | #265, # TERN Ecological Data Vocabularies 31 | #266, # IBRA7 32 | #263, # TERN Platform List 33 | #286, # TERN Instruments List 34 | ] -------------------------------------------------------------------------------- /worker.py: -------------------------------------------------------------------------------- 1 | import os 2 | from celery import Celery 3 | 4 | broker_url = os.getenv('CELERY_BROKER_URL', 'filesystem://') 5 | broker_dir = os.getenv('CELERY_BROKER_FOLDER', './broker') 6 | 7 | for f in ['out', 'processed']: 8 | if not os.path.exists(os.path.join(broker_dir, f)): 9 | os.makedirs(os.path.join(broker_dir, f)) 10 | 11 | 12 | app = Celery(__name__) 13 | app.conf.update({ 14 | 'broker_url': broker_url, 15 | 'broker_transport_options': { 16 | 'data_folder_in': os.path.join(broker_dir, 'out'), 17 | 'data_folder_out': os.path.join(broker_dir, 'out'), 18 | 'data_folder_processed': os.path.join(broker_dir, 'processed') 19 | }, 20 | 'imports': ('tasks',), 21 | 'result_persistent': False, 22 | 'task_serializer': 'json', 23 | 'result_serializer': 'json', 24 | 'accept_content': ['json']}) 25 | --------------------------------------------------------------------------------