From 418afa5b0b451a36d86a91dfffab92397df9e640 Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 2 Oct 2024 15:36:42 +0200 Subject: [PATCH 01/26] Rename example files --- ckanext/dcat/tests/shacl/test_shacl.py | 12 ++++++------ ...dcat_ap_2.json => ckan_full_dataset_dcat_ap.json} | 0 ...cy.json => ckan_full_dataset_dcat_ap_legacy.json} | 0 ...n => ckan_full_dataset_dcat_ap_vocabularies.json} | 0 4 files changed, 6 insertions(+), 6 deletions(-) rename examples/ckan/{ckan_full_dataset_dcat_ap_2.json => ckan_full_dataset_dcat_ap.json} (100%) rename examples/ckan/{ckan_full_dataset_dcat_ap_2_legacy.json => ckan_full_dataset_dcat_ap_legacy.json} (100%) rename examples/ckan/{ckan_full_dataset_dcat_ap_2_vocabularies.json => ckan_full_dataset_dcat_ap_vocabularies.json} (100%) diff --git a/ckanext/dcat/tests/shacl/test_shacl.py b/ckanext/dcat/tests/shacl/test_shacl.py index 7dbd8a5b..5455ae3e 100644 --- a/ckanext/dcat/tests/shacl/test_shacl.py +++ b/ckanext/dcat/tests/shacl/test_shacl.py @@ -60,7 +60,7 @@ def _results_count(results_graph): ) def test_validate_dcat_ap_2_graph_shapes(): - graph = graph_from_dataset("ckan_full_dataset_dcat_ap_2.json") + graph = graph_from_dataset("ckan_full_dataset_dcat_ap.json") # dcat-ap_2.1.1_shacl_shapes.ttl: constraints concerning existance, domain and # literal range, and cardinalities. @@ -84,7 +84,7 @@ def test_validate_dcat_ap_2_graph_shapes(): ) def test_validate_dcat_ap_2_graph_shapes_recommended(): - graph = graph_from_dataset("ckan_full_dataset_dcat_ap_2.json") + graph = graph_from_dataset("ckan_full_dataset_dcat_ap.json") # dcat-ap_2.1.1_shacl_shapes_recommended.ttl: constraints concerning existance # of recommended properties. @@ -99,7 +99,7 @@ def test_validate_dcat_ap_2_graph_shapes_recommended(): @pytest.mark.ckan_config("ckanext.dcat.rdf.profiles", "euro_dcat_ap_2") def test_validate_dcat_ap_2_legacy_graph_shapes(): - graph = graph_from_dataset("ckan_full_dataset_dcat_ap_2_legacy.json") + graph = graph_from_dataset("ckan_full_dataset_dcat_ap_legacy.json") # dcat-ap_2.1.1_shacl_shapes.ttl: constraints concerning existance, domain and # literal range, and cardinalities. @@ -114,7 +114,7 @@ def test_validate_dcat_ap_2_legacy_graph_shapes(): @pytest.mark.ckan_config("ckanext.dcat.rdf.profiles", "euro_dcat_ap_2") def test_validate_dcat_ap_2_legacy_graph_shapes_recommended(): - graph = graph_from_dataset("ckan_full_dataset_dcat_ap_2_legacy.json") + graph = graph_from_dataset("ckan_full_dataset_dcat_ap_legacy.json") # dcat-ap_2.1.1_shacl_shapes_recommended.ttl: constraints concerning existance # of recommended properties. @@ -138,7 +138,7 @@ def test_validate_dcat_ap_2_legacy_graph_shapes_recommended(): ) def test_validate_dcat_ap_2_graph_shapes_range(): - graph = graph_from_dataset("ckan_full_dataset_dcat_ap_2_vocabularies.json") + graph = graph_from_dataset("ckan_full_dataset_dcat_ap_vocabularies.json") # dcat-ap_2.1.1_shacl_range.ttl: constraints concerning object range path = _get_shacl_file_path("dcat-ap_2.1.1_shacl_range.ttl") @@ -176,7 +176,7 @@ def test_validate_dcat_ap_2_graph_shapes_range(): @pytest.mark.ckan_config("ckanext.dcat.rdf.profiles", "euro_dcat_ap_3") def test_validate_dcat_ap_3_graph(): - graph = graph_from_dataset("ckan_full_dataset_dcat_ap_2_vocabularies.json") + graph = graph_from_dataset("ckan_full_dataset_dcat_ap_vocabularies.json") path = _get_shacl_file_path("dcat-ap_3_shacl_shapes.ttl") r = validate(graph, shacl_graph=path) diff --git a/examples/ckan/ckan_full_dataset_dcat_ap_2.json b/examples/ckan/ckan_full_dataset_dcat_ap.json similarity index 100% rename from examples/ckan/ckan_full_dataset_dcat_ap_2.json rename to examples/ckan/ckan_full_dataset_dcat_ap.json diff --git a/examples/ckan/ckan_full_dataset_dcat_ap_2_legacy.json b/examples/ckan/ckan_full_dataset_dcat_ap_legacy.json similarity index 100% rename from examples/ckan/ckan_full_dataset_dcat_ap_2_legacy.json rename to examples/ckan/ckan_full_dataset_dcat_ap_legacy.json diff --git a/examples/ckan/ckan_full_dataset_dcat_ap_2_vocabularies.json b/examples/ckan/ckan_full_dataset_dcat_ap_vocabularies.json similarity index 100% rename from examples/ckan/ckan_full_dataset_dcat_ap_2_vocabularies.json rename to examples/ckan/ckan_full_dataset_dcat_ap_vocabularies.json From ebadc160ff7a14b73d70bc3ae4273c173272b67d Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 2 Oct 2024 15:43:06 +0200 Subject: [PATCH 02/26] Set things up for DCAT US profile --- ckanext/dcat/profiles/__init__.py | 1 + ckanext/dcat/profiles/dcat_us_3.py | 74 + .../tests/shacl/dcat-us_3.0_shacl_shapes.ttl | 3436 +++++++++++++++++ ckanext/dcat/tests/shacl/test_shacl.py | 37 + ...kan_full_dataset_dcat_us_vocabularies.json | 189 + setup.py | 1 + 6 files changed, 3738 insertions(+) create mode 100644 ckanext/dcat/profiles/dcat_us_3.py create mode 100644 ckanext/dcat/tests/shacl/dcat-us_3.0_shacl_shapes.ttl create mode 100644 examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json diff --git a/ckanext/dcat/profiles/__init__.py b/ckanext/dcat/profiles/__init__.py index 29bb0e78..958faa9c 100644 --- a/ckanext/dcat/profiles/__init__.py +++ b/ckanext/dcat/profiles/__init__.py @@ -21,5 +21,6 @@ from .euro_dcat_ap import EuropeanDCATAPProfile from .euro_dcat_ap_2 import EuropeanDCATAP2Profile from .euro_dcat_ap_3 import EuropeanDCATAP3Profile +from .dcat_us_3 import DCATUS3Profile from .euro_dcat_ap_scheming import EuropeanDCATAPSchemingProfile from .schemaorg import SchemaOrgProfile diff --git a/ckanext/dcat/profiles/dcat_us_3.py b/ckanext/dcat/profiles/dcat_us_3.py new file mode 100644 index 00000000..60466761 --- /dev/null +++ b/ckanext/dcat/profiles/dcat_us_3.py @@ -0,0 +1,74 @@ +from rdflib import Literal, BNode + +from ckanext.dcat.profiles import ( + DCAT, + XSD, + SKOS, + ADMS, + RDF, +) + +from .euro_dcat_ap_2 import EuropeanDCATAP2Profile +from .euro_dcat_ap_scheming import EuropeanDCATAPSchemingProfile + + +class DCATUS3Profile(EuropeanDCATAP2Profile, EuropeanDCATAPSchemingProfile): + """ + An RDF profile based on the DCAT-US 3 for data portals in the US + """ + + def parse_dataset(self, dataset_dict, dataset_ref): + + # Call base method for common properties + dataset_dict = self._parse_dataset_base(dataset_dict, dataset_ref) + + # DCAT AP v2 properties also applied to higher versions + dataset_dict = self._parse_dataset_v2(dataset_dict, dataset_ref) + + # DCAT AP v2 scheming fields + dataset_dict = self._parse_dataset_v2_scheming(dataset_dict, dataset_ref) + + return dataset_dict + + def graph_from_dataset(self, dataset_dict, dataset_ref): + + # Call base method for common properties + self._graph_from_dataset_base(dataset_dict, dataset_ref) + + # DCAT AP v2 properties also applied to higher versions + self._graph_from_dataset_v2(dataset_dict, dataset_ref) + + # DCAT AP v2 scheming fields + self._graph_from_dataset_v2_scheming(dataset_dict, dataset_ref) + + # DCAT AP v3 properties also applied to higher versions + self._graph_from_dataset_v3(dataset_dict, dataset_ref) + + def graph_from_catalog(self, catalog_dict, catalog_ref): + + self._graph_from_catalog_base(catalog_dict, catalog_ref) + + def _graph_from_dataset_v3(self, dataset_dict, dataset_ref): + + # byteSize decimal -> nonNegativeInteger + for subject, predicate, object in self.g.triples((None, DCAT.byteSize, None)): + if object and object.datatype == XSD.decimal: + self.g.remove((subject, predicate, object)) + + self.g.add( + ( + subject, + predicate, + Literal(int(object), datatype=XSD.nonNegativeInteger), + ) + ) + + # Other identifiers + value = self._get_dict_value(dataset_dict, "alternate_identifier") + if value: + items = self._read_list_value(value) + for item in items: + identifier = BNode() + self.g.add((dataset_ref, ADMS.identifier, identifier)) + self.g.add((identifier, RDF.type, ADMS.Identifier)) + self.g.add((identifier, SKOS.notation, Literal(item))) diff --git a/ckanext/dcat/tests/shacl/dcat-us_3.0_shacl_shapes.ttl b/ckanext/dcat/tests/shacl/dcat-us_3.0_shacl_shapes.ttl new file mode 100644 index 00000000..91ec2671 --- /dev/null +++ b/ckanext/dcat/tests/shacl/dcat-us_3.0_shacl_shapes.ttl @@ -0,0 +1,3436 @@ +@prefix : . +@prefix adms: . +@prefix cnt: . +@prefix dash: . +@prefix dc: . +@prefix dcat: . +@prefix dcat-us: . +@prefix dcat-us-shp: . +@prefix dcatap: . +@prefix dcterms: . +@prefix dqv: . +@prefix foaf: . +@prefix gsp: . +@prefix locn: . +@prefix odrl: . +@prefix odrs: . +@prefix org: . +@prefix owl: . +@prefix prov: . +@prefix rdf: . +@prefix rdfs: . +@prefix schema: . +@prefix sdmx-attribute: . +@prefix sh: . +@prefix skos: . +@prefix spdx: . +@prefix vcard: . +@prefix xsd: . + +dcat-us-shp:AccessRestriction_Shape a sh:NodeShape ; + rdfs:label "Access Restriction" ; + sh:property dcat-us-shp:AccessRestriction_Shape-restrictionNote, + dcat-us-shp:AccessRestriction_Shape-restrictionStatus, + dcat-us-shp:AccessRestriction_Shape-specificRestriction ; + sh:targetClass dcat-us:AccessRestriction . + +dcat-us-shp:Activity_Shape a sh:NodeShape ; + rdfs:label "Activity" ; + sh:property dcat-us-shp:Activity_Shape-category, + dcat-us-shp:Activity_Shape-label ; + sh:targetClass prov:Activity . + +dcat-us-shp:Address_Contact_Shape a sh:NodeShape ; + rdfs:label "Address (Contact Point)" ; + sh:property dcat-us-shp:Address_Contact_Shape-country-name, + dcat-us-shp:Address_Contact_Shape-locality, + dcat-us-shp:Address_Contact_Shape-postal-code, + dcat-us-shp:Address_Contact_Shape-region, + dcat-us-shp:Address_Contact_Shape-street-address ; + sh:targetClass vcard:Address . + +dcat-us-shp:Address_Location_Shape a sh:NodeShape ; + rdfs:label "Address (Location)" ; + sh:property dcat-us-shp:Address_Location_Shape-adminUnitL1, + dcat-us-shp:Address_Location_Shape-adminUnitL2, + dcat-us-shp:Address_Location_Shape-postCode, + dcat-us-shp:Address_Location_Shape-postName, + dcat-us-shp:Address_Location_Shape-thoroughfare ; + sh:targetClass locn:Address . + +dcat-us-shp:Agent_Shape a sh:NodeShape ; + rdfs:label "Agent" ; + sh:property dcat-us-shp:Agent_Shape-category, + dcat-us-shp:Agent_Shape-name ; + sh:targetClass foaf:Agent . + +dcat-us-shp:Attribution_Shape a sh:NodeShape ; + rdfs:label "Attribution" ; + sh:property dcat-us-shp:Attribution_Shape-agent, + dcat-us-shp:Attribution_Shape-hadRole ; + sh:targetClass prov:Attribution . + +dcat-us-shp:CUIRestriction a sh:NodeShape ; + rdfs:label "CUI Restriction" ; + sh:property dcat-us-shp:CUIRestriction-cuiBannerMarking, + dcat-us-shp:CUIRestriction-designationIndicator, + dcat-us-shp:CUIRestriction-requiredIndicatorPerAuthority ; + sh:targetClass dcat-us:CUIRestriction . + +dcat-us-shp:CatalogRecord_Shape a sh:NodeShape ; + rdfs:label "Catalog Record" ; + sh:property dcat-us-shp:CatalogRecord_Shape-conformsTo, + dcat-us-shp:CatalogRecord_Shape-description, + dcat-us-shp:CatalogRecord_Shape-issued, + dcat-us-shp:CatalogRecord_Shape-language, + dcat-us-shp:CatalogRecord_Shape-modified, + dcat-us-shp:CatalogRecord_Shape-primaryTopic, + dcat-us-shp:CatalogRecord_Shape-source, + dcat-us-shp:CatalogRecord_Shape-status, + dcat-us-shp:CatalogRecord_Shape-title ; + sh:targetClass dcat:CatalogRecord . + +dcat-us-shp:Catalog_Shape a sh:NodeShape ; + rdfs:label "Catalog" ; + sh:property dcat-us-shp:Catalog_Shape-accessRights, + dcat-us-shp:Catalog_Shape-catalog, + dcat-us-shp:Catalog_Shape-category, + dcat-us-shp:Catalog_Shape-conformsTo, + dcat-us-shp:Catalog_Shape-contactPoint, + dcat-us-shp:Catalog_Shape-creator, + dcat-us-shp:Catalog_Shape-dataset, + dcat-us-shp:Catalog_Shape-description, + dcat-us-shp:Catalog_Shape-hasPart, + dcat-us-shp:Catalog_Shape-homepage, + dcat-us-shp:Catalog_Shape-identifier, + dcat-us-shp:Catalog_Shape-issued, + dcat-us-shp:Catalog_Shape-keyword, + dcat-us-shp:Catalog_Shape-language, + dcat-us-shp:Catalog_Shape-license, + dcat-us-shp:Catalog_Shape-modified, + dcat-us-shp:Catalog_Shape-publisher, + dcat-us-shp:Catalog_Shape-qualifiedAttribution, + dcat-us-shp:Catalog_Shape-record, + dcat-us-shp:Catalog_Shape-rights, + dcat-us-shp:Catalog_Shape-rightsHolder, + dcat-us-shp:Catalog_Shape-service, + dcat-us-shp:Catalog_Shape-spatial, + dcat-us-shp:Catalog_Shape-subject, + dcat-us-shp:Catalog_Shape-temporal, + dcat-us-shp:Catalog_Shape-theme, + dcat-us-shp:Catalog_Shape-themeTaxonomy, + dcat-us-shp:Catalog_Shape-title ; + sh:targetClass dcat:Catalog . + +dcat-us-shp:Checksum_Shape a sh:NodeShape ; + rdfs:label "Checksum" ; + sh:property dcat-us-shp:Checksum_Shape-algorithm, + dcat-us-shp:Checksum_Shape-checksumValue ; + sh:targetClass spdx:Checksum . + +dcat-us-shp:ConceptScheme_Shape a sh:NodeShape ; + rdfs:label "Concept Scheme" ; + sh:property dcat-us-shp:ConceptScheme_Shape-created, + dcat-us-shp:ConceptScheme_Shape-description, + dcat-us-shp:ConceptScheme_Shape-issued, + dcat-us-shp:ConceptScheme_Shape-modified, + dcat-us-shp:ConceptScheme_Shape-title, + dcat-us-shp:ConceptScheme_Shape-version ; + sh:targetClass skos:ConceptScheme . + +dcat-us-shp:Concept_Shape a sh:NodeShape ; + rdfs:label "Concept" ; + sh:property dcat-us-shp:Concept_Shape-altLabel, + dcat-us-shp:Concept_Shape-definition, + dcat-us-shp:Concept_Shape-inScheme, + dcat-us-shp:Concept_Shape-notation, + dcat-us-shp:Concept_Shape-prefLabel ; + sh:targetClass skos:Concept . + +dcat-us-shp:DataService_Shape a sh:NodeShape ; + rdfs:label "Data Service" ; + sh:property dcat-us-shp:DataService_Shape-accessRights, + dcat-us-shp:DataService_Shape-category, + dcat-us-shp:DataService_Shape-conformsTo, + dcat-us-shp:DataService_Shape-contactPoint, + dcat-us-shp:DataService_Shape-created, + dcat-us-shp:DataService_Shape-creator, + dcat-us-shp:DataService_Shape-description, + dcat-us-shp:DataService_Shape-endpointDescription, + dcat-us-shp:DataService_Shape-endpointURL, + dcat-us-shp:DataService_Shape-geographicBoundingBox, + dcat-us-shp:DataService_Shape-hasPolicy, + dcat-us-shp:DataService_Shape-hasQualityMeasurement, + dcat-us-shp:DataService_Shape-identifier, + dcat-us-shp:DataService_Shape-keyword, + dcat-us-shp:DataService_Shape-language, + dcat-us-shp:DataService_Shape-license, + dcat-us-shp:DataService_Shape-modified, + dcat-us-shp:DataService_Shape-publisher, + dcat-us-shp:DataService_Shape-qualifiedAttribution, + dcat-us-shp:DataService_Shape-rights, + dcat-us-shp:DataService_Shape-rightsHolder, + dcat-us-shp:DataService_Shape-servesDataset, + dcat-us-shp:DataService_Shape-spatial, + dcat-us-shp:DataService_Shape-spatialResolutionInMeters, + dcat-us-shp:DataService_Shape-temporal, + dcat-us-shp:DataService_Shape-temporalResolution, + dcat-us-shp:DataService_Shape-theme, + dcat-us-shp:DataService_Shape-title, + dcat-us-shp:DataService_Shape-wasUsedBy ; + sh:targetClass dcat:DataService . + +dcat-us-shp:DatasetSeries_Shape a sh:NodeShape ; + rdfs:label "Dataset Series" ; + sh:property dcat-us-shp:DatasetSeries_Shape-accrualPeriodicity, + dcat-us-shp:DatasetSeries_Shape-contactPoint, + dcat-us-shp:DatasetSeries_Shape-description, + dcat-us-shp:DatasetSeries_Shape-first, + dcat-us-shp:DatasetSeries_Shape-issued, + dcat-us-shp:DatasetSeries_Shape-last, + dcat-us-shp:DatasetSeries_Shape-modified, + dcat-us-shp:DatasetSeries_Shape-publisher, + dcat-us-shp:DatasetSeries_Shape-seriesMember, + dcat-us-shp:DatasetSeries_Shape-spatial, + dcat-us-shp:DatasetSeries_Shape-temporal, + dcat-us-shp:DatasetSeries_Shape-title ; + sh:targetClass dcat:DatasetSeries . + +dcat-us-shp:Dataset_Shape a sh:NodeShape ; + rdfs:label "Dataset" ; + sh:property dcat-us-shp:Dataset_Shape-accessRights, + dcat-us-shp:Dataset_Shape-accrualPeriodicity, + dcat-us-shp:Dataset_Shape-category, + dcat-us-shp:Dataset_Shape-conformsTo, + dcat-us-shp:Dataset_Shape-contactPoint, + dcat-us-shp:Dataset_Shape-contributor, + dcat-us-shp:Dataset_Shape-created, + dcat-us-shp:Dataset_Shape-creator, + dcat-us-shp:Dataset_Shape-describedBy, + dcat-us-shp:Dataset_Shape-description, + dcat-us-shp:Dataset_Shape-distribution, + dcat-us-shp:Dataset_Shape-first, + dcat-us-shp:Dataset_Shape-geographicBoundingBox, + dcat-us-shp:Dataset_Shape-hasCurrentVersion, + dcat-us-shp:Dataset_Shape-hasPart, + dcat-us-shp:Dataset_Shape-hasQualityMeasurement, + dcat-us-shp:Dataset_Shape-hasVersion, + dcat-us-shp:Dataset_Shape-identifier, + dcat-us-shp:Dataset_Shape-image, + dcat-us-shp:Dataset_Shape-inSeries, + dcat-us-shp:Dataset_Shape-isReferencedBy, + dcat-us-shp:Dataset_Shape-issued, + dcat-us-shp:Dataset_Shape-keyword, + dcat-us-shp:Dataset_Shape-landingPage, + dcat-us-shp:Dataset_Shape-language, + dcat-us-shp:Dataset_Shape-liabilityStatement, + dcat-us-shp:Dataset_Shape-metadataDistribution, + dcat-us-shp:Dataset_Shape-modified, + dcat-us-shp:Dataset_Shape-otherIdentifier, + dcat-us-shp:Dataset_Shape-page, + dcat-us-shp:Dataset_Shape-previousVersion, + dcat-us-shp:Dataset_Shape-provenance, + dcat-us-shp:Dataset_Shape-publisher, + dcat-us-shp:Dataset_Shape-purpose, + dcat-us-shp:Dataset_Shape-qualifiedAttribution, + dcat-us-shp:Dataset_Shape-qualifiedRelation, + dcat-us-shp:Dataset_Shape-relation, + dcat-us-shp:Dataset_Shape-replaces, + dcat-us-shp:Dataset_Shape-rights, + dcat-us-shp:Dataset_Shape-rightsHolder, + dcat-us-shp:Dataset_Shape-sample, + dcat-us-shp:Dataset_Shape-scopeNote, + dcat-us-shp:Dataset_Shape-source, + dcat-us-shp:Dataset_Shape-spatial, + dcat-us-shp:Dataset_Shape-spatialResolutionInMeters, + dcat-us-shp:Dataset_Shape-status, + dcat-us-shp:Dataset_Shape-subject, + dcat-us-shp:Dataset_Shape-supportedSchema, + dcat-us-shp:Dataset_Shape-temporal, + dcat-us-shp:Dataset_Shape-temporalResolution, + dcat-us-shp:Dataset_Shape-theme, + dcat-us-shp:Dataset_Shape-title, + dcat-us-shp:Dataset_Shape-version, + dcat-us-shp:Dataset_Shape-versionNotes, + dcat-us-shp:Dataset_Shape-wasAttributedTo, + dcat-us-shp:Dataset_Shape-wasGeneratedBy, + dcat-us-shp:Dataset_Shape-wasUsedBy ; + sh:targetClass dcat:Dataset . + +dcat-us-shp:Distribution_Shape a sh:NodeShape ; + rdfs:label "Distribution", + "Document" ; + sh:property dcat-us-shp:Distribution_Shape-accessRestriction, + dcat-us-shp:Distribution_Shape-accessRights, + dcat-us-shp:Distribution_Shape-accessService, + dcat-us-shp:Distribution_Shape-accessURL, + dcat-us-shp:Distribution_Shape-availability, + dcat-us-shp:Distribution_Shape-byteSize, + dcat-us-shp:Distribution_Shape-characterEncoding, + dcat-us-shp:Distribution_Shape-checksum, + dcat-us-shp:Distribution_Shape-compressFormat, + dcat-us-shp:Distribution_Shape-conformsTo, + dcat-us-shp:Distribution_Shape-cuiRestriction, + dcat-us-shp:Distribution_Shape-describedBy, + dcat-us-shp:Distribution_Shape-description, + dcat-us-shp:Distribution_Shape-downloadURL, + dcat-us-shp:Distribution_Shape-format, + dcat-us-shp:Distribution_Shape-hasQualityMeasurement, + dcat-us-shp:Distribution_Shape-identifier, + dcat-us-shp:Distribution_Shape-image, + dcat-us-shp:Distribution_Shape-issued, + dcat-us-shp:Distribution_Shape-language, + dcat-us-shp:Distribution_Shape-license, + dcat-us-shp:Distribution_Shape-mediaType, + dcat-us-shp:Distribution_Shape-modified, + dcat-us-shp:Distribution_Shape-packageFormat, + dcat-us-shp:Distribution_Shape-page, + dcat-us-shp:Distribution_Shape-representationTechnique, + dcat-us-shp:Distribution_Shape-rights, + dcat-us-shp:Distribution_Shape-spatialResolutionInMeters, + dcat-us-shp:Distribution_Shape-status, + dcat-us-shp:Distribution_Shape-temporalResolution, + dcat-us-shp:Distribution_Shape-title, + dcat-us-shp:Distribution_Shape-useRestriction ; + sh:targetClass dcat:Distribution . + +dcat-us-shp:Document_Shape a sh:NodeShape ; + rdfs:label "Document" ; + sh:property dcat-us-shp:Document_Shape-abstract, + dcat-us-shp:Document_Shape-bibliographicCitation, + dcat-us-shp:Document_Shape-category, + dcat-us-shp:Document_Shape-conformsTo, + dcat-us-shp:Document_Shape-creator, + dcat-us-shp:Document_Shape-creators, + dcat-us-shp:Document_Shape-description, + dcat-us-shp:Document_Shape-identifier, + dcat-us-shp:Document_Shape-issued, + dcat-us-shp:Document_Shape-mediaType, + dcat-us-shp:Document_Shape-publisher, + dcat-us-shp:Document_Shape-publishers, + dcat-us-shp:Document_Shape-title ; + sh:targetClass foaf:Document . + +dcat-us-shp:GeographicBoundingBox_Shape a sh:NodeShape ; + rdfs:label "Geographic Bounding Box" ; + sh:property dcat-us-shp:GeographicBoundingBox_Shape-eastBoundingLongitude, + dcat-us-shp:GeographicBoundingBox_Shape-northBoundingLatitude, + dcat-us-shp:GeographicBoundingBox_Shape-southBoundingLatitude, + dcat-us-shp:GeographicBoundingBox_Shape-westBoundingLongitude ; + sh:targetClass dcat-us:GeographicBoundingBox . + +dcat-us-shp:Identifier_Shape a sh:NodeShape ; + rdfs:label "Identifier" ; + sh:property dcat-us-shp:Identifier_Shape-creator, + dcat-us-shp:Identifier_Shape-issued, + dcat-us-shp:Identifier_Shape-notation, + dcat-us-shp:Identifier_Shape-schemaAgency, + dcat-us-shp:Identifier_Shape-version ; + sh:targetClass adms:Identifier . + +dcat-us-shp:Kind_Shape a sh:NodeShape ; + rdfs:label "Contact" ; + sh:property dcat-us-shp:Kind_Shape-address, + dcat-us-shp:Kind_Shape-family-name, + dcat-us-shp:Kind_Shape-fn, + dcat-us-shp:Kind_Shape-given-name, + dcat-us-shp:Kind_Shape-hasEmail, + dcat-us-shp:Kind_Shape-organization-name, + dcat-us-shp:Kind_Shape-tel, + dcat-us-shp:Kind_Shape-title ; + sh:targetClass vcard:Kind . + +dcat-us-shp:LiabilityStatement_Shape a sh:NodeShape ; + rdfs:label "Liability Statement" ; + sh:property dcat-us-shp:LiabilityStatement_Shape-label ; + sh:targetClass dcat-us:LiabilityStatement . + +dcat-us-shp:LicenseDocument_Shape a sh:NodeShape ; + rdfs:label "License Document" ; + sh:property dcat-us-shp:LicenseDocument_Shape-licenseText ; + sh:targetClass dcterms:LicenseDocument . + +dcat-us-shp:Location_Shape a sh:NodeShape ; + rdfs:label "Location" ; + sh:property dcat-us-shp:Location_Shape-altLabel, + dcat-us-shp:Location_Shape-bbox, + dcat-us-shp:Location_Shape-centroid, + dcat-us-shp:Location_Shape-geometry, + dcat-us-shp:Location_Shape-identifier, + dcat-us-shp:Location_Shape-inScheme, + dcat-us-shp:Location_Shape-prefLabel ; + sh:targetClass dcterms:Location . + +dcat-us-shp:MediaType_Shape a sh:NodeShape ; + rdfs:label "MediaType" ; + sh:property dcat-us-shp:MediaType_Shape-label ; + sh:targetClass dcterms:MediaType . + +dcat-us-shp:Metric_Shape a sh:NodeShape ; + rdfs:label "Metric" ; + sh:property dcat-us-shp:Metric_Shape-expectedDataType, + dcat-us-shp:Metric_Shape-inDimension ; + sh:targetClass dqv:Metric . + +dcat-us-shp:Organization_Shape a sh:NodeShape ; + rdfs:label "Organization", + "Person" ; + sh:property dcat-us-shp:Organization_Shape-altLabel, + dcat-us-shp:Organization_Shape-name, + dcat-us-shp:Organization_Shape-notation, + dcat-us-shp:Organization_Shape-prefLabel, + dcat-us-shp:Organization_Shape-subOrganizationOf ; + sh:targetClass org:Organization . + +dcat-us-shp:PeriodOfTime_Shape a sh:NodeShape ; + rdfs:label "Period of Time" ; + sh:property dcat-us-shp:PeriodOfTime_Shape-endDate, + dcat-us-shp:PeriodOfTime_Shape-startDate ; + sh:targetClass dcterms:PeriodOfTime . + +dcat-us-shp:Person_Shape a sh:NodeShape ; + rdfs:label "Person" ; + sh:property dcat-us-shp:Person_Shape-firstname, + dcat-us-shp:Person_Shape-givenName, + dcat-us-shp:Person_Shape-memberOf, + dcat-us-shp:Person_Shape-name ; + sh:targetClass foaf:Person . + +dcat-us-shp:ProvenanceStatement_Shape a sh:NodeShape ; + rdfs:label "Provenance Statement" ; + sh:property dcat-us-shp:ProvenanceStatement_Shape-label ; + sh:targetClass dcterms:ProvenanceStatement . + +dcat-us-shp:QualityMeasurement_Shape a sh:NodeShape ; + rdfs:label "Quality Measurement" ; + sh:property dcat-us-shp:QualityMeasurement_Shape-isMeasurementOf, + dcat-us-shp:QualityMeasurement_Shape-unitMeasure, + dcat-us-shp:QualityMeasurement_Shape-value ; + sh:targetClass dqv:QualityMeasurement . + +dcat-us-shp:Relationship_Shape a sh:NodeShape ; + rdfs:label "Relationship" ; + sh:property dcat-us-shp:Relationship_Shape-hadRole, + dcat-us-shp:Relationship_Shape-relation ; + sh:targetClass dcat:Relationship . + +dcat-us-shp:RightsStatement_Shape a sh:NodeShape ; + rdfs:label "Rights Statement" ; + sh:property dcat-us-shp:RightsStatement_Shape-attributionText ; + sh:targetClass dcterms:RightsStatement . + +dcat-us-shp:Standard_Shape a sh:NodeShape ; + rdfs:label "Standard" ; + sh:property dcat-us-shp:Standard_Shape-category, + dcat-us-shp:Standard_Shape-created, + dcat-us-shp:Standard_Shape-description, + dcat-us-shp:Standard_Shape-identifier, + dcat-us-shp:Standard_Shape-inScheme, + dcat-us-shp:Standard_Shape-issued, + dcat-us-shp:Standard_Shape-modified, + dcat-us-shp:Standard_Shape-title ; + sh:targetClass dcterms:Standard . + +dcat-us-shp:UseRestriction_Shape a sh:NodeShape ; + rdfs:label "Use Restriction" ; + sh:property dcat-us-shp:UseRestriction_Shape-restrictionNote, + dcat-us-shp:UseRestriction_Shape-restrictionStatus, + dcat-us-shp:UseRestriction_Shape-specificRestriction ; + sh:targetClass dcat-us:UseRestriction . + +dash:DateOrDateTime a rdf:List ; + rdf:first [ sh:datatype xsd:date ] ; + rdf:rest ( [ sh:datatype xsd:dateTime ] ) . + +: a owl:Ontology ; + dcterms:conformsTo ; + dcterms:creator ; + dcterms:description "This document specifies the constraints on properties and classes expressed by DCAT-US 3.0 in SHACL." ; + dcterms:license ; + dcterms:publisher ; + dcterms:title "DCAT 3.0 Application Profile" ; + owl:imports sh: ; + owl:versionInfo "3.0.0" ; + foaf:homepage . + +dcat-us-shp:AccessRestriction_Shape-restrictionNote a sh:PropertyShape ; + rdfs:label "restriction note" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A note related to the access restriction" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "restrictionNote" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcat-us:restrictionNote ; + sh:severity sh:Violation . + +dcat-us-shp:AccessRestriction_Shape-restrictionStatus a sh:PropertyShape ; + rdfs:label "restriction status" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "The indication of whether or not there are access restrictions on the data." ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "restrictionStatus" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path dcat-us:restrictionStatus ; + sh:severity sh:Violation . + +dcat-us-shp:AccessRestriction_Shape-specificRestriction a sh:PropertyShape ; + rdfs:label "specific restriction" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The specific NARA restriction associated with the access restriction" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "specificRestriction" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path dcat-us:specificRestriction ; + sh:severity sh:Violation . + +dcat-us-shp:Activity_Shape-category a sh:PropertyShape ; + rdfs:label "category" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The category of the Activity " ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "category" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path dcterms:type ; + sh:severity sh:Violation . + +dcat-us-shp:Activity_Shape-label a sh:PropertyShape ; + rdfs:label "label" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "This property is used to give a human-readable label for the activity." ; + sh:minCount 0 ; + sh:name "label" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path rdfs:label ; + sh:severity sh:Violation . + +dcat-us-shp:Address_Contact_Shape-country-name a sh:PropertyShape ; + rdfs:label "country" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The country of an Address of the Kind." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "country-name" ; + sh:nodeKind sh:Literal ; + sh:path vcard:country-name ; + sh:severity sh:Violation . + +dcat-us-shp:Address_Contact_Shape-locality a sh:PropertyShape ; + rdfs:label "locality" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The city of an Address of the Kind." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "locality" ; + sh:nodeKind sh:Literal ; + sh:path vcard:locality ; + sh:severity sh:Violation . + +dcat-us-shp:Address_Contact_Shape-postal-code a sh:PropertyShape ; + rdfs:label "postal code" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The postal code of an Address of the Kind." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "postal-code" ; + sh:nodeKind sh:Literal ; + sh:path vcard:postal-code ; + sh:severity sh:Violation . + +dcat-us-shp:Address_Contact_Shape-region a sh:PropertyShape ; + rdfs:label "administrative area" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The administrative area of an Address of the Kind. Depending on the country, this corresponds to a province, a county, a region, or a state." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "region" ; + sh:nodeKind sh:Literal ; + sh:path vcard:region ; + sh:severity sh:Violation . + +dcat-us-shp:Address_Contact_Shape-street-address a sh:PropertyShape ; + rdfs:label "street address" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The street name and civic number of an Address of the Kind." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "street-address" ; + sh:nodeKind sh:Literal ; + sh:path vcard:street-address ; + sh:severity sh:Violation . + +dcat-us-shp:Address_Location_Shape-adminUnitL1 a sh:PropertyShape ; + rdfs:label "country" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The country of an Address of the Agent." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "adminUnitL1" ; + sh:nodeKind sh:Literal ; + sh:path locn:adminUnitL1 ; + sh:severity sh:Violation . + +dcat-us-shp:Address_Location_Shape-adminUnitL2 a sh:PropertyShape ; + rdfs:label "administrative area" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The administrative area of an Address of the Agent. Depending on the country, this corresponds to a province, a county, a region, or a state." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "adminUnitL2" ; + sh:nodeKind sh:Literal ; + sh:path locn:adminUnitL2 ; + sh:severity sh:Violation . + +dcat-us-shp:Address_Location_Shape-postCode a sh:PropertyShape ; + rdfs:label "post code" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The post code of an Address of the Agent." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "postCode" ; + sh:nodeKind sh:Literal ; + sh:path locn:postCode ; + sh:severity sh:Violation . + +dcat-us-shp:Address_Location_Shape-postName a sh:PropertyShape ; + rdfs:label "locality" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The key postal division of the address, usually the city." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "postName" ; + sh:nodeKind sh:Literal ; + sh:path locn:postName ; + sh:severity sh:Violation . + +dcat-us-shp:Address_Location_Shape-thoroughfare a sh:PropertyShape ; + rdfs:label "street address" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The street name and civic number of an Address of the Agent." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "thoroughfare" ; + sh:nodeKind sh:Literal ; + sh:path locn:thoroughfare ; + sh:severity sh:Violation . + +dcat-us-shp:Agent_Shape-category a sh:PropertyShape ; + rdfs:label "category" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property refers to a type of the Agent that makes the Catalog, Catalog Record, Data Service, or Dataset available" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "category" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path dcterms:type ; + sh:severity sh:Violation . + +dcat-us-shp:Agent_Shape-name a sh:PropertyShape ; + rdfs:label "name" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:datatype xsd:string ; + sh:description "The name of the software agent" ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "name" ; + sh:nodeKind sh:Literal ; + sh:path foaf:name ; + sh:severity sh:Violation . + +dcat-us-shp:Attribution_Shape-agent a sh:PropertyShape ; + rdfs:label "agent" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "The agent property references an Agent that plays a role in the resource" ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "agent" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class foaf:Agent ] ) ; + sh:path prov:agent ; + sh:severity sh:Violation . + +dcat-us-shp:Attribution_Shape-hadRole a sh:PropertyShape ; + rdfs:label "role" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "The function of an entity or agent with respect to another entity or resource." ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "hadRole" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Role ] ) ; + sh:path dcat:hadRole ; + sh:severity sh:Violation . + +dcat-us-shp:CUIRestriction-cuiBannerMarking a sh:PropertyShape ; + rdfs:label "CUI banner marking" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:datatype xsd:string ; + sh:description "CUI (Controlled Unclassified Information) banner marking is required for any unclassified information that is deemed sensitive and requires protection." ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "cuiBannerMarking" ; + sh:nodeKind sh:Literal ; + sh:path dcat-us:cuiBannerMarking ; + sh:severity sh:Violation . + +dcat-us-shp:CUIRestriction-designationIndicator a sh:PropertyShape ; + rdfs:label "CUI designation indicator" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:datatype xsd:string ; + sh:description "Designation Indicator shows which agency made the document CUI" ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "designationIndicator" ; + sh:nodeKind sh:Literal ; + sh:path dcat-us:designationIndicator ; + sh:severity sh:Violation . + +dcat-us-shp:CUIRestriction-requiredIndicatorPerAuthority a sh:PropertyShape ; + rdfs:label "required indicator per authority" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:datatype xsd:string ; + sh:description "free text (e.g., text of the category description or the distribution statement)" ; + sh:minCount 0 ; + sh:name "requiredIndicatorPerAuthority" ; + sh:nodeKind sh:Literal ; + sh:path dcat-us:requiredIndicatorPerAuthority ; + sh:severity sh:Violation . + +dcat-us-shp:CatalogRecord_Shape-conformsTo a sh:PropertyShape ; + rdfs:label "application profile" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "An Application Profile that the Catalog Record's metadata conforms to." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "conformsTo" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:Standard ] ) ; + sh:path dcterms:conformsTo ; + sh:severity sh:Violation . + +dcat-us-shp:CatalogRecord_Shape-description a sh:PropertyShape ; + rdfs:label "description" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A free-text account of the Catalog Record. This property can be repeated for parallel language versions of the description." ; + sh:minCount 0 ; + sh:name "description" ; + sh:nodeKind sh:Literal ; + sh:path dcterms:description ; + sh:severity sh:Violation . + +dcat-us-shp:CatalogRecord_Shape-issued a sh:PropertyShape ; + rdfs:label "listing date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The date on which the description of the Dataset was included in the Catalog." ; + sh:minCount 0 ; + sh:name "issued" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:issued ; + sh:severity sh:Violation . + +dcat-us-shp:CatalogRecord_Shape-language a sh:PropertyShape ; + rdfs:label "language" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A language used in the textual metadata describing titles, descriptions, etc. of the Catalog Record." ; + sh:minCount 0 ; + sh:name "language" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcterms:LinguisticSystem ] ) ; + sh:path dcterms:language ; + sh:severity sh:Violation . + +dcat-us-shp:CatalogRecord_Shape-modified a sh:PropertyShape ; + rdfs:label "update/modification date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "The most recent date on which the Catalog Record's entry was changed or modified." ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "modified" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:modified ; + sh:severity sh:Violation . + +dcat-us-shp:CatalogRecord_Shape-primaryTopic a sh:PropertyShape ; + rdfs:label "primary topic" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "A link to the Dataset, Data service or Catalog described in the Catalog Record." ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "primaryTopic" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcat:Resource ] ) ; + sh:path foaf:primaryTopic ; + sh:severity sh:Violation . + +dcat-us-shp:CatalogRecord_Shape-source a sh:PropertyShape ; + rdfs:label "source metadata" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The original metadata that was used in creating metadata for the datasets, data services, or catalogs in the Catalog Record." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "source" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Resource ] ) ; + sh:path dcterms:source ; + sh:severity sh:Violation . + +dcat-us-shp:CatalogRecord_Shape-status a sh:PropertyShape ; + rdfs:label "change type" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The status of the catalog record in the context of editorial flow of the dataset and data service descriptions." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "status" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path adms:status ; + sh:severity sh:Violation . + +dcat-us-shp:CatalogRecord_Shape-title a sh:PropertyShape ; + rdfs:label "title" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A name given to the Catalog Record." ; + sh:minCount 0 ; + sh:name "title" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:title ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-accessRights a sh:PropertyShape ; + rdfs:label "access rights" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to information that indicates whether the Catalog is open data, has access restrictions or is not public." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "accessRights" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:RightsStatement ] ) ; + sh:path dcterms:accessRights ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-catalog a sh:PropertyShape ; + rdfs:label "catalog" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to a catalog whose contents are of interest in the context of this catalog." ; + sh:minCount 0 ; + sh:name "catalog" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Catalog ] ) ; + sh:path dcat:catalog ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-category a sh:PropertyShape ; + rdfs:label "category" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The category of the Catalog " ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "category" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path dcterms:type ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-conformsTo a sh:PropertyShape ; + rdfs:label "schema version" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "An established standard to which the described catalog conforms." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "conformsTo" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:Standard ] ) ; + sh:path dcterms:conformsTo ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-contactPoint a sh:PropertyShape ; + rdfs:label "contact point" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property contains contact information that can be used for sending comments about the Catalogue." ; + sh:minCount 0 ; + sh:name "contactPoint" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class vcard:Kind ] ) ; + sh:path dcat:contactPoint ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-creator a sh:PropertyShape ; + rdfs:label "creator" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The entity responsible for creating the resource." ; + sh:minCount 0 ; + sh:name "creator" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class foaf:Agent ] ) ; + sh:path dcterms:creator ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-dataset a sh:PropertyShape ; + rdfs:label "dataset" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "This property links the Catalog with the Dataset(s) that is/are part of the Catalog." ; + sh:minCount 1 ; + sh:name "dataset" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Dataset ] ) ; + sh:path dcat:dataset ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-description a sh:PropertyShape ; + rdfs:label "description" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "Free-text description of the catalog (in the language indicated in the attribute)." ; + sh:minCount 1 ; + sh:name "description" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:description ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-hasPart a sh:PropertyShape ; + rdfs:label "has part" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to a related catalog that is part of the described catalog." ; + sh:minCount 0 ; + sh:name "hasPart" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Catalog ] ) ; + sh:path dcterms:hasPart ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-homepage a sh:PropertyShape ; + rdfs:label "homepage" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "A homepage of the catalog (a public Web document usually available in HTML)." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "homepage" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class foaf:Document ] ) ; + sh:path foaf:homepage ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-identifier a sh:PropertyShape ; + rdfs:label "identifier" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property contains the main identifier for the Catalog, e.g. the URI or other unique identifier." ; + sh:minCount 0 ; + sh:name "identifier" ; + sh:nodeKind sh:Literal ; + sh:path dcterms:identifier ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-issued a sh:PropertyShape ; + rdfs:label "release date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "Date of formal issuance (e.g., publication) of the resource." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "issued" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:issued ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-keyword a sh:PropertyShape ; + rdfs:label "keyword/tag" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A keyword or tag describing the resource." ; + sh:minCount 0 ; + sh:name "keyword" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcat:keyword ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-language a sh:PropertyShape ; + rdfs:label "language" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property refers to a language used in the textual metadata describing titles, descriptions, etc. of the Datasets in the Catalog." ; + sh:minCount 0 ; + sh:name "language" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcterms:LinguisticSystem ] ) ; + sh:path dcterms:language ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-license a sh:PropertyShape ; + rdfs:label "license" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property refers to the license under which the Catalog can be used or reused." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "license" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:LicenseDocument ] ) ; + sh:path dcterms:license ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-modified a sh:PropertyShape ; + rdfs:label "update/modification date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "Most recent date on which the resource was changed, updated or modified." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "modified" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:modified ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-publisher a sh:PropertyShape ; + rdfs:label "publisher" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "Entity responsible for making the catalog available." ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "publisher" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class foaf:Agent ] ) ; + sh:path dcterms:publisher ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-qualifiedAttribution a sh:PropertyShape ; + rdfs:label "qualified attribution" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to a link to an Agent having some form of responsibility for the Catalog." ; + sh:minCount 0 ; + sh:name "qualifiedAttribution" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class prov:Attribution ] ) ; + sh:path prov:qualifiedAttribution ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-record a sh:PropertyShape ; + rdfs:label "catalog record" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A record describing the registration of a single resource (e.g., a dataset, a data service) that is part of the catalog." ; + sh:minCount 0 ; + sh:name "record" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:CatalogRecord ] ) ; + sh:path dcat:record ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-rights a sh:PropertyShape ; + rdfs:label "rights" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to a statement that specifies rights associated with the Catalogue." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "rights" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:RightsStatement ] ) ; + sh:path dcterms:rights ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-rightsHolder a sh:PropertyShape ; + rdfs:label "rights holder" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to an organization holding rights on the Catalog." ; + sh:minCount 0 ; + sh:name "rightsHolder" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class org:Organization ] ) ; + sh:path dcterms:rightsHolder ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-service a sh:PropertyShape ; + rdfs:label "service" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to a site or end-point (Data Service) that is listed in the Catalog." ; + sh:minCount 0 ; + sh:name "service" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:DataService ] ) ; + sh:path dcat:service ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-spatial a sh:PropertyShape ; + rdfs:label "spatial/geographic coverage" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The geographical area covered by the catalog." ; + sh:minCount 0 ; + sh:name "spatial" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:Location ] ) ; + sh:path dcterms:spatial ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-subject a sh:PropertyShape ; + rdfs:label "subject" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Primary subject of the catalog" ; + sh:minCount 0 ; + sh:name "subject" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path dcterms:subject ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-temporal a sh:PropertyShape ; + rdfs:label "temporal coverage" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A temporal period that the Catalog covers." ; + sh:minCount 0 ; + sh:name "temporal" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:PeriodOfTime ] ) ; + sh:path dcterms:temporal ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-theme a sh:PropertyShape ; + rdfs:label "theme/category" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to a category of the Catalog. A Catalog may be associated with multiple themes." ; + sh:minCount 0 ; + sh:name "theme" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path dcat:theme ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-themeTaxonomy a sh:PropertyShape ; + rdfs:label "themes" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "A knowledge organization system (KOS) used to classify the resources documented in the catalog (e.g., datasets and services)." ; + sh:minCount 0 ; + sh:name "themeTaxonomy" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:ConceptScheme ] ) ; + sh:path dcat:themeTaxonomy ; + sh:severity sh:Violation . + +dcat-us-shp:Catalog_Shape-title a sh:PropertyShape ; + rdfs:label "title" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "The title of the catalog in the indicated language" ; + sh:minCount 1 ; + sh:name "title" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:title ; + sh:severity sh:Violation . + +dcat-us-shp:Checksum_Shape-algorithm a sh:PropertyShape ; + rdfs:label "algorithm" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "The algorithm used to produce the subject Checksum." ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "algorithm" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class spdx:ChecksumAlgorithm ] ) ; + sh:path spdx:algorithm . + +dcat-us-shp:Checksum_Shape-checksumValue a sh:PropertyShape ; + rdfs:label "checksum value" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:datatype xsd:hexBinary ; + sh:description "A lower case hexadecimal encoded digest value produced using a specific algorithm." ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "checksumValue" ; + sh:nodeKind sh:Literal ; + sh:path spdx:checksumValue . + +dcat-us-shp:ConceptScheme_Shape-created a sh:PropertyShape ; + rdfs:label "creation date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property contains the date on which the Concept Scheme has been first created." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "created" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:created ; + sh:severity sh:Violation . + +dcat-us-shp:ConceptScheme_Shape-description a sh:PropertyShape ; + rdfs:label "description" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property contains a description of the Concept Scheme. " ; + sh:minCount 0 ; + sh:name "description" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:description ; + sh:severity sh:Violation . + +dcat-us-shp:ConceptScheme_Shape-issued a sh:PropertyShape ; + rdfs:label "publication date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property contains the date of formal issuance (e.g., publication) of the Concept Scheme." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "issued" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:issued ; + sh:severity sh:Violation . + +dcat-us-shp:ConceptScheme_Shape-modified a sh:PropertyShape ; + rdfs:label "update/modification date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property contains the most recent date at which the Concept Scheme was changed or modified." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "modified" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:modified ; + sh:severity sh:Violation . + +dcat-us-shp:ConceptScheme_Shape-title a sh:PropertyShape ; + rdfs:label "title" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "The title of the concept scheme in the indicated language." ; + sh:minCount 1 ; + sh:name "title" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:title ; + sh:severity sh:Violation . + +dcat-us-shp:ConceptScheme_Shape-version a sh:PropertyShape ; + rdfs:label "version info" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property contains a version number or other version designation of the Concept Scheme." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "version" ; + sh:nodeKind sh:Literal ; + sh:path dcat:version ; + sh:severity sh:Violation . + +dcat-us-shp:Concept_Shape-altLabel a sh:PropertyShape ; + rdfs:label "alternate label" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Alternative labels for a concept." ; + sh:minCount 0 ; + sh:name "altLabel" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path skos:altLabel ; + sh:severity sh:Violation . + +dcat-us-shp:Concept_Shape-definition a sh:PropertyShape ; + rdfs:label "definition" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "Definition of the controlled vocabulary term." ; + sh:minCount 0 ; + sh:name "definition" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path skos:definition ; + sh:severity sh:Violation . + +dcat-us-shp:Concept_Shape-inScheme a sh:PropertyShape ; + rdfs:label "in scheme" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description """ +Concept scheme defining the concept.""" ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "inScheme" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:ConceptScheme ] ) ; + sh:path skos:inScheme ; + sh:severity sh:Violation . + +dcat-us-shp:Concept_Shape-notation a sh:PropertyShape ; + rdfs:label "notation" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "abbreviations or codes from code lists for an organization." ; + sh:minCount 0 ; + sh:name "notation" ; + sh:nodeKind sh:Literal ; + sh:path skos:notation ; + sh:severity sh:Violation . + +dcat-us-shp:Concept_Shape-prefLabel a sh:PropertyShape ; + rdfs:label "preferred label" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "Preferred label for the controlled vocabulary term (one per language)." ; + sh:minCount 1 ; + sh:name "prefLabel" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path skos:prefLabel ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-accessRights a sh:PropertyShape ; + rdfs:label "access rights" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property MAY include information regarding access or restrictions based on privacy, security, or other policies." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "accessRights" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:RightsStatement ] ) ; + sh:path dcterms:accessRights ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-category a sh:PropertyShape ; + rdfs:label "category" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Category of the data service" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "category" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path dcterms:type ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-conformsTo a sh:PropertyShape ; + rdfs:label "conforms to" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property is used to indicate the general standard or specification that the Data Service endpoints implement." ; + sh:minCount 0 ; + sh:name "conformsTo" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:Standard ] ) ; + sh:path dcterms:conformsTo ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-contactPoint a sh:PropertyShape ; + rdfs:label "contact point" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "This property contains contact information that can be used for sending comments about the Data Service." ; + sh:minCount 1 ; + sh:name "contactPoint" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class vcard:Kind ] ) ; + sh:path dcat:contactPoint ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-created a sh:PropertyShape ; + rdfs:label "creation date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property contains the date on which the Data Service has been first created." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "created" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:created ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-creator a sh:PropertyShape ; + rdfs:label "creator" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to the Agent primarily responsible for producing the Data Service." ; + sh:minCount 0 ; + sh:name "creator" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class foaf:Agent ] ) ; + sh:path dcterms:creator ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-description a sh:PropertyShape ; + rdfs:label "description" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property contains a free-text account of the Data Service." ; + sh:minCount 0 ; + sh:name "description" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:description ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-endpointDescription a sh:PropertyShape ; + rdfs:label "endpoint description" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "A description of the services available via the end-points, including their operations, parameters etc." ; + sh:minCount 0 ; + sh:name "endpointDescription" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class rdfs:Resource ] ) ; + sh:path dcat:endpointDescription ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-endpointURL a sh:PropertyShape ; + rdfs:label "endpoint URL" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "The root location or primary endpoint of the service (a Web-resolvable IRI)" ; + sh:minCount 1 ; + sh:name "endpointURL" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class rdfs:Resource ] ) ; + sh:path dcat:endpointURL ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-geographicBoundingBox a sh:PropertyShape ; + rdfs:label "geographic bounding box" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property describes the spatial extent of domain of application of an data service and is standardized in WGS 84 Lat/Long coordinate system." ; + sh:minCount 0 ; + sh:name "geographicBoundingBox" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcat-us:GeographicBoundingBox ] ) ; + sh:path dcat-us:geographicBoundingBox ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-hasPolicy a sh:PropertyShape ; + rdfs:label "has policy" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "An ODRL conformant policy expressing the rights associated with the Data Service." ; + sh:minCount 0 ; + sh:name "hasPolicy" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class odrl:Policy ] ) ; + sh:path odrl:hasPolicy ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-hasQualityMeasurement a sh:PropertyShape ; + rdfs:label "quality measurement" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Refers to the performed quality measurements." ; + sh:minCount 0 ; + sh:name "hasQualityMeasurement" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dqv:QualityMeasurement ] ) ; + sh:path dqv:hasQualityMeasurement ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-identifier a sh:PropertyShape ; + rdfs:label "identifier" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property contains the main identifier for the Data Service, e.g. the URI or other unique identifier in the context of the Catalog." ; + sh:minCount 0 ; + sh:name "identifier" ; + sh:nodeKind sh:Literal ; + sh:path dcterms:identifier ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-keyword a sh:PropertyShape ; + rdfs:label "keyword/tag" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property contains a keyword or tag describing the Data Service." ; + sh:minCount 0 ; + sh:name "keyword" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcat:keyword ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-language a sh:PropertyShape ; + rdfs:label "language" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to a language supported by the Data Service. This property can be repeated if multiple languages are supported in the Data Service." ; + sh:minCount 0 ; + sh:name "language" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcterms:LinguisticSystem ] ) ; + sh:path dcterms:language ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-license a sh:PropertyShape ; + rdfs:label "license" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property refers to the license under which the Data Service is made available." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "license" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:LicenseDocument ] ) ; + sh:path dcterms:license ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-modified a sh:PropertyShape ; + rdfs:label "update/modification date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property contains the most recent date on which the Data Service was changed or modified." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "modified" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:modified ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-publisher a sh:PropertyShape ; + rdfs:label "publisher" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "This property refers to an entity (organization) responsible for making the Data Service available." ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "publisher" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class foaf:Agent ] ) ; + sh:path dcterms:publisher ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-qualifiedAttribution a sh:PropertyShape ; + rdfs:label "qualified attribution" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to a link to an Agent having some form of responsibility for the DataService." ; + sh:minCount 0 ; + sh:name "qualifiedAttribution" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class prov:Attribution ] ) ; + sh:path prov:qualifiedAttribution ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-rights a sh:PropertyShape ; + rdfs:label "rights" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A statement that concerns all rights for the Data Service not addressed with dcterms:license or dcterms:accessRights, such as copyright statements." ; + sh:minCount 0 ; + sh:name "rights" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:RightsStatement ] ) ; + sh:path dcterms:rights ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-rightsHolder a sh:PropertyShape ; + rdfs:label "rights holder" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to an Agent (organization) holding rights on the Data Service." ; + sh:minCount 0 ; + sh:name "rightsHolder" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class org:Organization ] ) ; + sh:path dcterms:rightsHolder ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-servesDataset a sh:PropertyShape ; + rdfs:label "serves dataset" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The Dataset that is served by this data service." ; + sh:minCount 0 ; + sh:name "servesDataset" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Dataset ] ) ; + sh:path dcat:servesDataset ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-spatial a sh:PropertyShape ; + rdfs:label "spatial/geographic coverage" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to a geographic region that is covered by the Data Service." ; + sh:minCount 0 ; + sh:name "spatial" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:Location ] ) ; + sh:path dcterms:spatial ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-spatialResolutionInMeters a sh:PropertyShape ; + rdfs:label "spatial resolution in meters" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:datatype xsd:decimal ; + sh:description "This property refers to the minimum spatial separation resolvable in a Data Service, measured in meters." ; + sh:minCount 0 ; + sh:name "spatialResolutionInMeters" ; + sh:nodeKind sh:Literal ; + sh:path dcat:spatialResolutionInMeters ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-temporal a sh:PropertyShape ; + rdfs:label "temporal coverage" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A temporal period that the DataService covers." ; + sh:minCount 0 ; + sh:name "temporal" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:PeriodOfTime ] ) ; + sh:path dcterms:temporal ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-temporalResolution a sh:PropertyShape ; + rdfs:label "temporal resolution" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The minimum time period resolvable by the Data Service." ; + sh:minCount 0 ; + sh:name "temporalResolution" ; + sh:nodeKind sh:Literal ; + sh:path dcat:temporalResolution ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-theme a sh:PropertyShape ; + rdfs:label "theme/category" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to a theme of the Data Service. A Data Service may be associated with multiple themes." ; + sh:minCount 0 ; + sh:name "theme" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path dcat:theme ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-title a sh:PropertyShape ; + rdfs:label "title" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "The title of the data service in the indicated language" ; + sh:minCount 1 ; + sh:name "title" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:title ; + sh:severity sh:Violation . + +dcat-us-shp:DataService_Shape-wasUsedBy a sh:PropertyShape ; + rdfs:label "was used by" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to an Activity that used the Data Service." ; + sh:minCount 0 ; + sh:name "wasUsedBy" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class prov:Activity ] ) ; + sh:path prov:wasUsedBy ; + sh:severity sh:Violation . + +dcat-us-shp:DatasetSeries_Shape-accrualPeriodicity a sh:PropertyShape ; + rdfs:label "frequency" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to the frequency at which the Dataset Series is updated." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "accrualPeriodicity" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcterms:Frequency ] ) ; + sh:path dcterms:accrualPeriodicity ; + sh:severity sh:Violation . + +dcat-us-shp:DatasetSeries_Shape-contactPoint a sh:PropertyShape ; + rdfs:label "contact point" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property contains contact information that can be used for sending comments about the Dataset Series." ; + sh:minCount 0 ; + sh:name "contactPoint" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class vcard:Kind ] ) ; + sh:path dcat:contactPoint ; + sh:severity sh:Violation . + +dcat-us-shp:DatasetSeries_Shape-description a sh:PropertyShape ; + rdfs:label "description" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "A free-text account of the Dataset Series." ; + sh:minCount 1 ; + sh:name "description" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:description ; + sh:severity sh:Violation . + +dcat-us-shp:DatasetSeries_Shape-first a sh:PropertyShape ; + rdfs:label "first" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The first dataset in an ordered dataset series, to which the current dataset belongs." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "first" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Dataset ] ) ; + sh:path dcat:first ; + sh:severity sh:Violation . + +dcat-us-shp:DatasetSeries_Shape-issued a sh:PropertyShape ; + rdfs:label "release date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The date of formal issuance (e.g.,publication) of the Dataset Series." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "issued" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:issued ; + sh:severity sh:Violation . + +dcat-us-shp:DatasetSeries_Shape-last a sh:PropertyShape ; + rdfs:label "last" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The last resource in an ordered dataset series" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "last" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Dataset ] ) ; + sh:path dcat:last ; + sh:severity sh:Violation . + +dcat-us-shp:DatasetSeries_Shape-modified a sh:PropertyShape ; + rdfs:label "update/modification date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property contains the most recent date on which the Dataset Series was changed or modified." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "modified" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:modified ; + sh:severity sh:Violation . + +dcat-us-shp:DatasetSeries_Shape-publisher a sh:PropertyShape ; + rdfs:label "publisher" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "An entity (organization) responsible for ensuring the coherency of the Dataset Series." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "publisher" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class foaf:Agent ] ) ; + sh:path dcterms:publisher ; + sh:severity sh:Violation . + +dcat-us-shp:DatasetSeries_Shape-seriesMember a sh:PropertyShape ; + rdfs:label "series member" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "A member of the Dataset Series." ; + sh:minCount 0 ; + sh:name "seriesMember" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Dataset ] ) ; + sh:path dcat:seriesMember ; + sh:severity sh:Violation . + +dcat-us-shp:DatasetSeries_Shape-spatial a sh:PropertyShape ; + rdfs:label "spatial/geographic coverage" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property refers to a geographic region that is covered by the Dataset Series." ; + sh:minCount 0 ; + sh:name "spatial" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:Location ] ) ; + sh:path dcterms:spatial ; + sh:severity sh:Violation . + +dcat-us-shp:DatasetSeries_Shape-temporal a sh:PropertyShape ; + rdfs:label "temporal coverage" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "A temporal period that the Dataset Series covers." ; + sh:minCount 0 ; + sh:name "temporal" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:PeriodOfTime ] ) ; + sh:path dcterms:temporal ; + sh:severity sh:Violation . + +dcat-us-shp:DatasetSeries_Shape-title a sh:PropertyShape ; + rdfs:label "title" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "A name given to the Dataset Series." ; + sh:minCount 1 ; + sh:name "title" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:title ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-accessRights a sh:PropertyShape ; + rdfs:label "access rights" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Information that indicates whether the Dataset is open data, has access restrictions or is public." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "accessRights" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:RightsStatement ] ) ; + sh:path dcterms:accessRights ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-accrualPeriodicity a sh:PropertyShape ; + rdfs:label "frequency" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to the frequency at which the Dataset is updated." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "accrualPeriodicity" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcterms:Frequency ] ) ; + sh:path dcterms:accrualPeriodicity ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-category a sh:PropertyShape ; + rdfs:label "category" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Category of the dataset" ; + sh:minCount 0 ; + sh:name "category" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path dcterms:type ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-conformsTo a sh:PropertyShape ; + rdfs:label "conforms to" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to an standard which the described Dataset conforms." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "conformsTo" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:Standard ] ) ; + sh:path dcterms:conformsTo ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-contactPoint a sh:PropertyShape ; + rdfs:label "contact point" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property contains contact information that can be used for sending comments about the Dataset." ; + sh:minCount 0 ; + sh:name "contactPoint" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class vcard:Kind ] ) ; + sh:path dcat:contactPoint ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-contributor a sh:PropertyShape ; + rdfs:label "contributor" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to an agent contributing to the Dataset." ; + sh:minCount 0 ; + sh:name "contributor" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:Agent ] ) ; + sh:path dcterms:contributor ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-created a sh:PropertyShape ; + rdfs:label "creation date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property contains the date on which the Dataset has been first created." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "created" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:created ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-creator a sh:PropertyShape ; + rdfs:label "creator" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to an entity responsible for producing the dataset." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "creator" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class foaf:Agent ] ) ; + sh:path dcterms:creator ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-describedBy a sh:PropertyShape ; + rdfs:label "data dictionary" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property refers to a distribution describing the Data Dictionary." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "describedBy" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Distribution ] ) ; + sh:path dcat-us:describedBy . + +dcat-us-shp:Dataset_Shape-description a sh:PropertyShape ; + rdfs:label "description" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "This property contains a free-text account of the Dataset. " ; + sh:minCount 1 ; + sh:name "description" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:description ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-distribution a sh:PropertyShape ; + rdfs:label "dataset distribution" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property links the Dataset to an available Distribution." ; + sh:minCount 0 ; + sh:name "distribution" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcat:Distribution ] ) ; + sh:path dcat:distribution ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-first a sh:PropertyShape ; + rdfs:label "first" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "the first item of the sequence the dataset belongs to" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "first" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Dataset ] ) ; + sh:path dcat:first ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-geographicBoundingBox a sh:PropertyShape ; + rdfs:label "geographic bounding box" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "WGS84 Geographic Bounding Box for this dataset" ; + sh:minCount 0 ; + sh:name "geographicBoundingBox" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcat-us:GeographicBoundingBox ] ) ; + sh:path dcat-us:geographicBoundingBox ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-hasCurrentVersion a sh:PropertyShape ; + rdfs:label "current version" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "reference to the current (latest) version of a dataset" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "hasCurrentVersion" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Dataset ] ) ; + sh:path dcat:hasCurrentVersion ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-hasPart a sh:PropertyShape ; + rdfs:label "has part" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to a related dataset that is part of the described dataset." ; + sh:minCount 0 ; + sh:name "hasPart" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Dataset ] ) ; + sh:path dcterms:hasPart ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-hasQualityMeasurement a sh:PropertyShape ; + rdfs:label "quality measurement" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "property referring to a quality measurement for the dataset" ; + sh:minCount 0 ; + sh:name "hasQualityMeasurement" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dqv:QualityMeasurement ] ) ; + sh:path dqv:hasQualityMeasurement ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-hasVersion a sh:PropertyShape ; + rdfs:label "has version" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A related Dataset that is a version, edition, or adaptation of the described Dataset. " ; + sh:minCount 0 ; + sh:name "hasVersion" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Dataset ] ) ; + sh:path dcat:hasVersion ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-identifier a sh:PropertyShape ; + rdfs:label "identifier" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property contains the unique identifier for the Dataset, e.g. the URI or other unique identifier in the context of the Catalog." ; + sh:minCount 0 ; + sh:name "identifier" ; + sh:nodeKind sh:Literal ; + sh:path dcterms:identifier ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-image a sh:PropertyShape ; + rdfs:label "image" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A thumbnail picture illustrating the content of the dataset." ; + sh:minCount 0 ; + sh:name "image" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class schema:ImageObject ] ) ; + sh:path schema:image ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-inSeries a sh:PropertyShape ; + rdfs:label "in series" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Dataset Series this dataset belong to" ; + sh:minCount 0 ; + sh:name "inSeries" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:DatasetSeries ] ) ; + sh:path dcat:inSeries ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-isReferencedBy a sh:PropertyShape ; + rdfs:label "is referenced by" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property is about a related resource, such as a publication, that references, cites, or otherwise points to the Dataset." ; + sh:minCount 0 ; + sh:name "isReferencedBy" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class rdfs:Resource ] ) ; + sh:path dcterms:isReferencedBy ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-issued a sh:PropertyShape ; + rdfs:label "release date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "Date of formal issuance (e.g., publication) of the dataset." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "issued" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:issued ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-keyword a sh:PropertyShape ; + rdfs:label "keyword/tag" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property contains a keyword or tag describing the Dataset." ; + sh:minCount 0 ; + sh:name "keyword" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcat:keyword ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-landingPage a sh:PropertyShape ; + rdfs:label "landing page" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property refers to a web page that provides access to the Dataset, its Distributions and/or additional information." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "landingPage" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class foaf:Document ] ) ; + sh:path dcat:landingPage ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-language a sh:PropertyShape ; + rdfs:label "language" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A language used in the Dataset." ; + sh:minCount 0 ; + sh:name "language" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcterms:LinguisticSystem ] ) ; + sh:path dcterms:language ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-liabilityStatement a sh:PropertyShape ; + rdfs:label "liability statement" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A liability statement about the dataset" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "liabilityStatement" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcat-us:LiabilityStatement ] ) ; + sh:path dcat-us:liabilityStatement ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-metadataDistribution a sh:PropertyShape ; + rdfs:label "metadata distribution" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Distribution to \"original\" metadata document" ; + sh:minCount 0 ; + sh:name "metadataDistribution" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Distribution ] ) ; + sh:path dcat-us:metadataDistribution ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-modified a sh:PropertyShape ; + rdfs:label "last modified" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The most recent date on which the Dataset was changed or modified." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "modified" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:modified ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-otherIdentifier a sh:PropertyShape ; + rdfs:label "other identifier" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "reference to a structure identifier." ; + sh:minCount 0 ; + sh:name "otherIdentifier" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class adms:Identifier ] ) ; + sh:path adms:identifier ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-page a sh:PropertyShape ; + rdfs:label "documentation" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A page or document about this dataset" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "page" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class foaf:Document ] ) ; + sh:path foaf:page ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-previousVersion a sh:PropertyShape ; + rdfs:label "previous version" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "previous dataset version " ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "previousVersion" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Dataset ] ) ; + sh:path dcat:previousVersion ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-provenance a sh:PropertyShape ; + rdfs:label "provenance" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A statement about the lineage of a Dataset" ; + sh:minCount 0 ; + sh:name "provenance" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:ProvenanceStatement ] ) ; + sh:path dcterms:provenance ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-publisher a sh:PropertyShape ; + rdfs:label "publisher" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "This property refers to an organisation responsible for making the Dataset available." ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "publisher" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class org:Organization ] ) ; + sh:path dcterms:publisher ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-purpose a sh:PropertyShape ; + rdfs:label "purpose" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The purpose of the dataset" ; + sh:minCount 0 ; + sh:name "purpose" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcat-us:purpose ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-qualifiedAttribution a sh:PropertyShape ; + rdfs:label "qualified attribution" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "An agent having some form of responsibility for the dataset" ; + sh:minCount 0 ; + sh:name "qualifiedAttribution" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class prov:Attribution ] ) ; + sh:path prov:qualifiedAttribution ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-qualifiedRelation a sh:PropertyShape ; + rdfs:label "qualified relation" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Qualified relationship with role of the dataset with another resource" ; + sh:minCount 0 ; + sh:name "qualifiedRelation" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcat:Relationship ] ) ; + sh:path dcat:qualifiedRelation ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-relation a sh:PropertyShape ; + rdfs:label "related resource" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Reference to a related source" ; + sh:minCount 0 ; + sh:name "relation" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class rdfs:Resource ] ) ; + sh:path dcterms:relation ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-replaces a sh:PropertyShape ; + rdfs:label "replaces" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Reference to the replaced Dataset" ; + sh:minCount 0 ; + sh:name "replaces" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Dataset ] ) ; + sh:path dcterms:replaces ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-rights a sh:PropertyShape ; + rdfs:label "rights" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A statement that concerns all rights for the Dataset not addressed with dcterms:license or dcterms:accessRights, such as copyright statements." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "rights" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:RightsStatement ] ) ; + sh:path dcterms:rights ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-rightsHolder a sh:PropertyShape ; + rdfs:label "rights holder" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to an Agent (organization) holding rights on the Dataset." ; + sh:minCount 0 ; + sh:name "rightsHolder" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class org:Organization ] ) ; + sh:path dcterms:rightsHolder ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-sample a sh:PropertyShape ; + rdfs:label "sample" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Links to a sample of an Dataset, which is a dcat:Distribution." ; + sh:minCount 0 ; + sh:name "sample" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Distribution ] ) ; + sh:path adms:sample ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-scopeNote a sh:PropertyShape ; + rdfs:label "usage note" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "usage note for the dataset" ; + sh:minCount 0 ; + sh:name "scopeNote" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path skos:scopeNote ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-source a sh:PropertyShape ; + rdfs:label "data source" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Reference to the source of the dataset" ; + sh:minCount 0 ; + sh:name "source" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class rdfs:Resource ] ) ; + sh:path dcterms:source ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-spatial a sh:PropertyShape ; + rdfs:label "spatial/geographic coverage" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property refers to a geographic region that is covered by the Dataset." ; + sh:minCount 0 ; + sh:name "spatial" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:Location ] ) ; + sh:path dcterms:spatial ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-spatialResolutionInMeters a sh:PropertyShape ; + rdfs:label "Spatial resolution (meters)" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:datatype xsd:decimal ; + sh:description "Spatial resolution in meters" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "spatialResolutionInMeters" ; + sh:nodeKind sh:Literal ; + sh:path dcat:spatialResolutionInMeters ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-status a sh:PropertyShape ; + rdfs:label "lifecycle status" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The status of the dataset in the context of maturity lifecycle." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "status" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path adms:status ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-subject a sh:PropertyShape ; + rdfs:label "subject" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Primary subject of the dataset" ; + sh:minCount 0 ; + sh:name "subject" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path dcterms:subject ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-supportedSchema a sh:PropertyShape ; + rdfs:label "supported schema" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "supported schema for this dataset" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "supportedSchema" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Dataset ] ) ; + sh:path adms:supportedSchema ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-temporal a sh:PropertyShape ; + rdfs:label "temporal coverage" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "A temporal period that the dataset covers." ; + sh:minCount 0 ; + sh:name "temporal" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:PeriodOfTime ] ) ; + sh:path dcterms:temporal ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-temporalResolution a sh:PropertyShape ; + rdfs:label "temporal resolution" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:datatype xsd:duration ; + sh:description "Temporal resolution using xsd:duration syntax" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "temporalResolution" ; + sh:nodeKind sh:Literal ; + sh:path dcat:temporalResolution ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-theme a sh:PropertyShape ; + rdfs:label "theme/category" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "A theme of the dataset" ; + sh:minCount 0 ; + sh:name "theme" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path dcat:theme ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-title a sh:PropertyShape ; + rdfs:label "title" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "This property contains a name given to the Dataset. This property can be repeated for parallel language versions of the name" ; + sh:minCount 1 ; + sh:name "title" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:title ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-version a sh:PropertyShape ; + rdfs:label "version" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The version indicator (name or identifier) of a resource" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "version" ; + sh:nodeKind sh:Literal ; + sh:path dcat:version ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-versionNotes a sh:PropertyShape ; + rdfs:label "version notes" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "version notes for this dataset" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "versionNotes" ; + sh:nodeKind sh:Literal ; + sh:path adms:versionNotes ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-wasAttributedTo a sh:PropertyShape ; + rdfs:label "attribution" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Agent attributed to this dataset" ; + sh:minCount 0 ; + sh:name "wasAttributedTo" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class foaf:Agent ] ) ; + sh:path prov:wasAttributedTo ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-wasGeneratedBy a sh:PropertyShape ; + rdfs:label "was generated by" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "An activity that generated, or provides the business context for, the creation of the dataset" ; + sh:minCount 0 ; + sh:name "wasGeneratedBy" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class prov:Activity ] ) ; + sh:path prov:wasGeneratedBy ; + sh:severity sh:Violation . + +dcat-us-shp:Dataset_Shape-wasUsedBy a sh:PropertyShape ; + rdfs:label "used by" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to an Activity that used the Dataset" ; + sh:minCount 0 ; + sh:name "wasUsedBy" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class prov:Activity ] ) ; + sh:path prov:wasUsedBy ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-accessRestriction a sh:PropertyShape ; + rdfs:label "access restriction" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Access restriction related to the distribution" ; + sh:minCount 0 ; + sh:name "accessRestriction" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcat-us:AccessRestriction ] ) ; + sh:path dcat-us:accessRestriction ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-accessRights a sh:PropertyShape ; + rdfs:label "access rights" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property MAY include information regarding access or restrictions based on privacy, security, or other policies." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "accessRights" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:RightsStatement ] ) ; + sh:path dcterms:accessRights ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-accessService a sh:PropertyShape ; + rdfs:label "access service" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A data service that gives access to the distribution of the dataset." ; + sh:minCount 0 ; + sh:name "accessService" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:DataService ] ) ; + sh:path dcat:accessService ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-accessURL a sh:PropertyShape ; + rdfs:label "access URL" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "A URL that gives access to a Distribution of the Dataset." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "accessURL" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class rdfs:Resource ] ) ; + sh:path dcat:accessURL ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-availability a sh:PropertyShape ; + rdfs:label "availability" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "An indication how long it is planned to keep the Distribution of the Dataset available." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "availability" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path dcatap:availability ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-byteSize a sh:PropertyShape ; + rdfs:label "byte size" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:datatype xsd:nonNegativeInteger ; + sh:description "The size of a Distribution in bytes." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "byteSize" ; + sh:nodeKind sh:Literal ; + sh:path dcat:byteSize ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-characterEncoding a sh:PropertyShape ; + rdfs:label "character encoding" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property SHOULD be used to specify the character encoding of the Distribution, by using as value the character set names in the IANA register " ; + sh:minCount 0 ; + sh:name "characterEncoding" ; + sh:nodeKind sh:Literal ; + sh:path cnt:characterEncoding ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-checksum a sh:PropertyShape ; + rdfs:label "checksum" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A mechanism that can be used to verify that the contents of a distribution have not changed." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "checksum" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class spdx:Checksum ] ) ; + sh:path spdx:checksum ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-compressFormat a sh:PropertyShape ; + rdfs:label "compression format" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The format of the file in which the data is contained in a compressed form, e.g. to reduce the size of the downloadable file" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "compressFormat" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:MediaType ] ) ; + sh:path dcat:compressFormat ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-conformsTo a sh:PropertyShape ; + rdfs:label "linked schemas" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to an established schema or reference systems which the described Distribution conforms." ; + sh:minCount 0 ; + sh:name "conformsTo" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:Standard ] ) ; + sh:path dcterms:conformsTo ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-cuiRestriction a sh:PropertyShape ; + rdfs:label "CUI restriction" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Controlled Unclassified Information restriction related to the distribution" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "cuiRestriction" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcat-us:CUIRestriction ] ) ; + sh:path dcat-us:cuiRestriction ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-describedBy a sh:PropertyShape ; + rdfs:label "data dictionary" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property refers to a distribution describing the Data Dictionary." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "describedBy" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Distribution ] ) ; + sh:path dcat-us:describedBy . + +dcat-us-shp:Distribution_Shape-description a sh:PropertyShape ; + rdfs:label "description" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "A free-text account of the Distribution." ; + sh:minCount 0 ; + sh:name "description" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:description ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-downloadURL a sh:PropertyShape ; + rdfs:label "download URL" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A URL that is a direct link to a downloadable file in a given format." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "downloadURL" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class rdfs:Resource ] ) ; + sh:path dcat:downloadURL ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-format a sh:PropertyShape ; + rdfs:label "format" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The file format of the Distribution." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "format" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:MediaType ] ) ; + sh:path dcterms:format ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-hasQualityMeasurement a sh:PropertyShape ; + rdfs:label "quality measurement" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "property referring to a quality measurement for the distribution" ; + sh:minCount 0 ; + sh:name "hasQualityMeasurement" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dqv:QualityMeasurement ] ) ; + sh:path dqv:hasQualityMeasurement ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-identifier a sh:PropertyShape ; + rdfs:label "identifier" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property contains a unique identifier for the Distribution (e.g. DOI, ISBN)" ; + sh:minCount 0 ; + sh:name "identifier" ; + sh:nodeKind sh:Literal ; + sh:path dcterms:identifier ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-image a sh:PropertyShape ; + rdfs:label "image" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A thumbnail picture illustrating the content of the distribution" ; + sh:minCount 0 ; + sh:name "image" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class schema:ImageObject ] ) ; + sh:path schema:image ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-issued a sh:PropertyShape ; + rdfs:label "release date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The date of formal issuance (e.g., publication) of the Distribution." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "issued" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:issued ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-language a sh:PropertyShape ; + rdfs:label "language" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A language used in the Distribution." ; + sh:minCount 0 ; + sh:name "language" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcterms:LinguisticSystem ] ) ; + sh:path dcterms:language ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-license a sh:PropertyShape ; + rdfs:label "license" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "A licence under which the Distribution is made available." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "license" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:LicenseDocument ] ) ; + sh:path dcterms:license ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-mediaType a sh:PropertyShape ; + rdfs:label "media type" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The media type of the Distribution as defined in the official register of media types managed by IANA." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "mediaType" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:MediaType ] ) ; + sh:path dcat:mediaType ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-modified a sh:PropertyShape ; + rdfs:label "last modified" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The most recent date on which the Distribution was changed or modified." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "modified" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:modified ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-packageFormat a sh:PropertyShape ; + rdfs:label "packaging format" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The format of the file in which one or more data files are grouped together, e.g. to enable a set of related files to be downloaded together." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "packageFormat" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:MediaType ] ) ; + sh:path dcat:packageFormat ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-page a sh:PropertyShape ; + rdfs:label "documentation" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "A page or document about this Distribution." ; + sh:minCount 0 ; + sh:name "page" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class foaf:Document ] ) ; + sh:path foaf:page ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-representationTechnique a sh:PropertyShape ; + rdfs:label "representation technique" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property MAY be used to provide more information about the format in which an Distribution is released. This is different from the file format as, for example, a ZIP file (file format) could contain an XML schema (representation technique). In DCAT-US profile, this property SHOULD be used to express the spatial representation type (grid, vector, tin), by using the URIs of the corresponding code list operated by an approved registry. " ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "representationTechnique" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path adms:representationTechnique ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-rights a sh:PropertyShape ; + rdfs:label "rights" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "A statement that specifies rights associated with the Distribution." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "rights" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:RightsStatement ] ) ; + sh:path dcterms:rights ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-spatialResolutionInMeters a sh:PropertyShape ; + rdfs:label "Spatial resolution (meters)" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:datatype xsd:decimal ; + sh:description "The minimum spatial separation resolvable in a dataset distribution, measured in meters." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "spatialResolutionInMeters" ; + sh:nodeKind sh:Literal ; + sh:path dcat:spatialResolutionInMeters ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-status a sh:PropertyShape ; + rdfs:label "lifecycle status" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The status of the distribution in the context of maturity lifecycle." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "status" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path adms:status ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-temporalResolution a sh:PropertyShape ; + rdfs:label "termporal resolution" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:datatype xsd:duration ; + sh:description "The minimum time period resolvable in the dataset distribution." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "temporalResolution" ; + sh:nodeKind sh:Literal ; + sh:path dcat:temporalResolution ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-title a sh:PropertyShape ; + rdfs:label "title" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "A name given to the Distribution." ; + sh:minCount 0 ; + sh:name "title" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:title ; + sh:severity sh:Violation . + +dcat-us-shp:Distribution_Shape-useRestriction a sh:PropertyShape ; + rdfs:label "use restriction" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Use restriction related to the distribution" ; + sh:minCount 0 ; + sh:name "useRestriction" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcat-us:UseRestriction ] ) ; + sh:path dcat-us:useRestriction ; + sh:severity sh:Violation . + +dcat-us-shp:Document_Shape-abstract a sh:PropertyShape ; + rdfs:label "abstract" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Text abstract of the document" ; + sh:minCount 0 ; + sh:name "abstract" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:abstract ; + sh:severity sh:Violation . + +dcat-us-shp:Document_Shape-bibliographicCitation a sh:PropertyShape ; + rdfs:label "bibliographic citation" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "Bibliographic citation as text" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "bibliographicCitation" ; + sh:nodeKind sh:Literal ; + sh:path dcterms:bibliographicCitation ; + sh:severity sh:Violation . + +dcat-us-shp:Document_Shape-category a sh:PropertyShape ; + rdfs:label "category" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "Category of the document" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "category" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path dcterms:type ; + sh:severity sh:Violation . + +dcat-us-shp:Document_Shape-conformsTo a sh:PropertyShape ; + rdfs:label "conforms to standard" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property refers to an standard which the described Document conforms." ; + sh:minCount 0 ; + sh:name "conformsTo" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class dcterms:Standard ] ) ; + sh:path dcterms:conformsTo ; + sh:severity sh:Violation . + +dcat-us-shp:Document_Shape-creator a sh:PropertyShape ; + rdfs:label "corporate author", + "individual author" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The organization responsible for creating the resource.", + "The person responsible for creating the document." ; + sh:minCount 0 ; + sh:name "creator" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class org:Organization ] ), + ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class foaf:Person ] ) ; + sh:path dcterms:creator ; + sh:severity sh:Violation . + +dcat-us-shp:Document_Shape-creators a sh:PropertyShape ; + rdfs:label "author(s) as literal" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "list of authors as a literal" ; + sh:minCount 0 ; + sh:name "creators" ; + sh:nodeKind sh:Literal ; + sh:path dc:creator ; + sh:severity sh:Violation . + +dcat-us-shp:Document_Shape-description a sh:PropertyShape ; + rdfs:label "description" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "A free-text account of the Document" ; + sh:minCount 0 ; + sh:name "description" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:description ; + sh:severity sh:Violation . + +dcat-us-shp:Document_Shape-identifier a sh:PropertyShape ; + rdfs:label "identifier" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property contains a unique identifier for the Document (e.g. DOI, ISBN)" ; + sh:minCount 0 ; + sh:name "identifier" ; + sh:nodeKind sh:Literal ; + sh:path dcterms:identifier ; + sh:severity sh:Violation . + +dcat-us-shp:Document_Shape-issued a sh:PropertyShape ; + rdfs:label "publication date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "publication date of the document" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "issued" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:issued ; + sh:severity sh:Violation . + +dcat-us-shp:Document_Shape-mediaType a sh:PropertyShape ; + rdfs:label "media type" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The file format of the Document." ; + sh:minCount 0 ; + sh:name "mediaType" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcterms:MediaType ] ) ; + sh:path dcat:mediaType ; + sh:severity sh:Violation . + +dcat-us-shp:Document_Shape-publisher a sh:PropertyShape ; + rdfs:label "publisher" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "publisher organization of the document" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "publisher" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class org:Organization ] ) ; + sh:path dcterms:publisher ; + sh:severity sh:Violation . + +dcat-us-shp:Document_Shape-publishers a sh:PropertyShape ; + rdfs:label "publisher(s) as literal" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "list of publishers as a literal" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "publishers" ; + sh:nodeKind sh:Literal ; + sh:path dc:publisher ; + sh:severity sh:Violation . + +dcat-us-shp:Document_Shape-title a sh:PropertyShape ; + rdfs:label "title" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "The title of the document in the indicated language" ; + sh:minCount 1 ; + sh:name "title" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:title ; + sh:severity sh:Violation . + +dcat-us-shp:GeographicBoundingBox_Shape-eastBoundingLongitude a sh:PropertyShape ; + rdfs:label "east bounding longitude" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:datatype xsd:decimal ; + sh:description "East bound longitude in decimal degrees" ; + sh:maxCount 1 ; + sh:maxInclusive 180.0 ; + sh:minCount 1 ; + sh:minInclusive -180.0 ; + sh:name "eastBoundingLongitude" ; + sh:nodeKind sh:Literal ; + sh:path dcat-us:eastBoundingLongitude ; + sh:severity sh:Violation . + +dcat-us-shp:GeographicBoundingBox_Shape-northBoundingLatitude a sh:PropertyShape ; + rdfs:label "north bounding latitude" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:datatype xsd:decimal ; + sh:description "North bound latitude in decimal degrees" ; + sh:maxCount 1 ; + sh:maxInclusive 90.0 ; + sh:minCount 1 ; + sh:minInclusive -90.0 ; + sh:name "northBoundingLatitude" ; + sh:nodeKind sh:Literal ; + sh:path dcat-us:northBoundingLatitude ; + sh:severity sh:Violation . + +dcat-us-shp:GeographicBoundingBox_Shape-southBoundingLatitude a sh:PropertyShape ; + rdfs:label "south bouding latitude" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:datatype xsd:decimal ; + sh:description "South bound latitude in decimal degrees" ; + sh:maxCount 1 ; + sh:maxInclusive 90.0 ; + sh:minCount 1 ; + sh:minInclusive -90.0 ; + sh:name "southBoundingLatitude" ; + sh:nodeKind sh:Literal ; + sh:path dcat-us:southBoundingLatitude ; + sh:severity sh:Violation . + +dcat-us-shp:GeographicBoundingBox_Shape-westBoundingLongitude a sh:PropertyShape ; + rdfs:label "west bounding longitude" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:datatype xsd:decimal ; + sh:description "West bound longitude in decimal degrees" ; + sh:maxCount 1 ; + sh:maxInclusive 180.0 ; + sh:minCount 1 ; + sh:minInclusive -180.0 ; + sh:name "westBoundingLongitude" ; + sh:nodeKind sh:Literal ; + sh:path dcat-us:westBoundingLongitude ; + sh:severity sh:Violation . + +dcat-us-shp:Identifier_Shape-creator a sh:PropertyShape ; + rdfs:label "creator" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "the agency that manages the identifier scheme" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "creator" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class org:Organization ] ) ; + sh:path dcterms:creator ; + sh:severity sh:Violation . + +dcat-us-shp:Identifier_Shape-issued a sh:PropertyShape ; + rdfs:label "issued" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The date of formal issuance (e.g., publication) of the Identiifer." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "issued" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:issued ; + sh:severity sh:Violation . + +dcat-us-shp:Identifier_Shape-notation a sh:PropertyShape ; + rdfs:label "notation" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "abbreviations or codes from code lists for an identifier" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "notation" ; + sh:nodeKind sh:Literal ; + sh:path skos:notation ; + sh:severity sh:Violation . + +dcat-us-shp:Identifier_Shape-schemaAgency a sh:PropertyShape ; + rdfs:label "schema agency" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The name of the agency that issued the identifier." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "schemaAgency" ; + sh:nodeKind sh:Literal ; + sh:path adms:schemaAgency ; + sh:severity sh:Violation . + +dcat-us-shp:Identifier_Shape-version a sh:PropertyShape ; + rdfs:label "version" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "version of the identifier scheme" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "version" ; + sh:nodeKind sh:Literal ; + sh:path dcterms:version ; + sh:severity sh:Violation . + +dcat-us-shp:Kind_Shape-address a sh:PropertyShape ; + rdfs:label "address" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property specifies the address of the contact" ; + sh:minCount 0 ; + sh:name "address" ; + sh:or ( [ sh:nodeKind sh:BlankNodeOrIRI ] [ sh:class vcard:Address ] ) ; + sh:path vcard:address ; + sh:severity sh:Violation . + +dcat-us-shp:Kind_Shape-family-name a sh:PropertyShape ; + rdfs:label "family name" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:datatype xsd:string ; + sh:description "This property specifies the family name of the person to contact" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "family-name" ; + sh:nodeKind sh:Literal ; + sh:path vcard:family-name . + +dcat-us-shp:Kind_Shape-fn a sh:PropertyShape ; + rdfs:label "formatted name" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:datatype xsd:string ; + sh:description "The formatted text corresponding to the name of the contact" ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "fn" ; + sh:nodeKind sh:Literal ; + sh:path vcard:fn ; + sh:severity sh:Violation . + +dcat-us-shp:Kind_Shape-given-name a sh:PropertyShape ; + rdfs:label "given name" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:datatype xsd:string ; + sh:description "This property specifies the given name of the person to contact" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "given-name" ; + sh:nodeKind sh:Literal ; + sh:path vcard:given-name . + +dcat-us-shp:Kind_Shape-hasEmail a sh:PropertyShape ; + rdfs:label "email" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "The email address of the contact" ; + sh:maxCount 1 ; + sh:message "Invalid email format. Should be a URI starting with mailto:" ; + sh:minCount 1 ; + sh:name "hasEmail" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class rdfs:Resource ] ) ; + sh:path vcard:hasEmail ; + sh:severity sh:Violation . + +dcat-us-shp:Kind_Shape-organization-name a sh:PropertyShape ; + rdfs:label "organization name" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:datatype xsd:string ; + sh:description "This property specifies the name of the organization to contact" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "organization-name" ; + sh:nodeKind sh:Literal ; + sh:path vcard:organization-name . + +dcat-us-shp:Kind_Shape-tel a sh:PropertyShape ; + rdfs:label "telephone" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property specifies the telephone number for telephony communication with the person or organization." ; + sh:maxCount 1 ; + sh:message "Invalid telephone format. Should be a URI starting with tel:" ; + sh:minCount 0 ; + sh:name "tel" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class rdfs:Resource ] ) ; + sh:path vcard:tel ; + sh:severity sh:Violation . + +dcat-us-shp:Kind_Shape-title a sh:PropertyShape ; + rdfs:label "position title" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:datatype xsd:string ; + sh:description "This property specifies the position role of the person to contact" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "title" ; + sh:nodeKind sh:Literal ; + sh:path vcard:title . + +dcat-us-shp:LiabilityStatement_Shape-label a sh:PropertyShape ; + rdfs:label "liability statement text" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Full text of the liability statement" ; + sh:minCount 0 ; + sh:name "label" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path rdfs:label ; + sh:severity sh:Violation . + +dcat-us-shp:LicenseDocument_Shape-licenseText a sh:PropertyShape ; + rdfs:label "license text" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Full text of the license." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "licenseText" ; + sh:nodeKind sh:Literal ; + sh:path spdx:licenseText ; + sh:severity sh:Violation . + +dcat-us-shp:Location_Shape-altLabel a sh:PropertyShape ; + rdfs:label "alternative names" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property provides alternative names for a location" ; + sh:minCount 0 ; + sh:name "altLabel" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path skos:altLabel . + +dcat-us-shp:Location_Shape-bbox a sh:PropertyShape ; + rdfs:label "bounding box" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "bounding box associated location (in any coordinate system)" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "bbox" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:GeometryAsWKTorGMLorGeoJSON ; + sh:path dcat:bbox ; + sh:severity sh:Violation . + +dcat-us-shp:Location_Shape-centroid a sh:PropertyShape ; + rdfs:label "centroid" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "The geographic center (centroid) of a spatial thing" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "centroid" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:GeometryAsWKTorGMLorGeoJSON ; + sh:path dcat:centroid ; + sh:severity sh:Violation . + +dcat-us-shp:Location_Shape-geometry a sh:PropertyShape ; + rdfs:label "geometry" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Associates a location with a corresponding geometry." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "geometry" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:GeometryAsWKTorGMLorGeoJSON ; + sh:path locn:geometry ; + sh:severity sh:Violation . + +dcat-us-shp:Location_Shape-identifier a sh:PropertyShape ; + rdfs:label "identifier" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property contains the geographic identifier for the Location, e.g., the URI or other unique identifier in the context of the relevant gazetteer." ; + sh:minCount 0 ; + sh:name "identifier" ; + sh:nodeKind sh:Literal ; + sh:path dcterms:identifier ; + sh:severity sh:Violation . + +dcat-us-shp:Location_Shape-inScheme a sh:PropertyShape ; + rdfs:label "gazetteer" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property MAY be used to specify the gazetteer to which the Location belongs." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "inScheme" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:ConceptScheme ] ) ; + sh:path skos:inScheme ; + sh:severity sh:Violation . + +dcat-us-shp:Location_Shape-prefLabel a sh:PropertyShape ; + rdfs:label "geographic name" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property contains a preferred label of the Location." ; + sh:minCount 0 ; + sh:name "prefLabel" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path skos:prefLabel ; + sh:severity sh:Violation . + +dcat-us-shp:MediaType_Shape-label a sh:PropertyShape ; + rdfs:label "label" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property contains the denomination of the Media Type." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "label" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path rdfs:label ; + sh:severity sh:Violation . + +dcat-us-shp:Metric_Shape-expectedDataType a sh:PropertyShape ; + rdfs:label "expected datatype" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "Represents the expected data type for the metric's observed value (e.g., xsd:boolean, xsd:double etc...)" ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "expectedDataType" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class xsd:anySimpleType ] ) ; + sh:path dqv:expectedDataType ; + sh:severity sh:Violation . + +dcat-us-shp:Metric_Shape-inDimension a sh:PropertyShape ; + rdfs:label "in dimension" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "Represents the dimensions a quality metric, certificate and annotation allow a measurement of." ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "inDimension" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dqv:Dimension ] ) ; + sh:path dqv:inDimension ; + sh:severity sh:Violation . + +dcat-us-shp:Organization_Shape-altLabel a sh:PropertyShape ; + rdfs:label "alternative label" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "alternative names (trading names, colloquial names) for an organization" ; + sh:minCount 0 ; + sh:name "altLabel" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path skos:altLabel ; + sh:severity sh:Violation . + +dcat-us-shp:Organization_Shape-name a sh:PropertyShape ; + rdfs:label "name" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:datatype xsd:string ; + sh:description "The full name of the Organization" ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "name" ; + sh:nodeKind sh:Literal ; + sh:path foaf:name ; + sh:severity sh:Violation . + +dcat-us-shp:Organization_Shape-notation a sh:PropertyShape ; + rdfs:label "notation" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "abbreviations or codes from code lists for an organization (e.g. DOI, DOD)" ; + sh:minCount 0 ; + sh:name "notation" ; + sh:nodeKind sh:Literal ; + sh:path skos:notation ; + sh:severity sh:Violation . + +dcat-us-shp:Organization_Shape-prefLabel a sh:PropertyShape ; + rdfs:label "preferred label" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "Preferred or legal name of the organization" ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "prefLabel" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path skos:prefLabel ; + sh:severity sh:Violation . + +dcat-us-shp:Organization_Shape-subOrganizationOf a sh:PropertyShape ; + rdfs:label "suborganization of" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Represents hierarchical containment of Organizations or OrganizationalUnits; indicates an Organization which contains this Organization." ; + sh:minCount 0 ; + sh:name "subOrganizationOf" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class org:Organization ] ) ; + sh:path org:subOrganizationOf ; + sh:severity sh:Violation . + +dcat-us-shp:PeriodOfTime_Shape-endDate a sh:PropertyShape ; + rdfs:label "end date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The end date of the period of time" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "endDate" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcat:endDate ; + sh:severity sh:Violation . + +dcat-us-shp:PeriodOfTime_Shape-startDate a sh:PropertyShape ; + rdfs:label "start date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The start date of the period of time" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "startDate" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcat:startDate ; + sh:severity sh:Violation . + +dcat-us-shp:Person_Shape-firstname a sh:PropertyShape ; + rdfs:label "first name" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:datatype xsd:string ; + sh:description "The first name of the Person" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "firstname" ; + sh:nodeKind sh:Literal ; + sh:path foaf:firstname ; + sh:severity sh:Violation . + +dcat-us-shp:Person_Shape-givenName a sh:PropertyShape ; + rdfs:label "given name" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:datatype xsd:string ; + sh:description "The given name of the Person" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "givenName" ; + sh:nodeKind sh:Literal ; + sh:path foaf:givenName ; + sh:severity sh:Violation . + +dcat-us-shp:Person_Shape-memberOf a sh:PropertyShape ; + rdfs:label "member of" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property MAY be used to specify the affiliation of the Person to an organization." ; + sh:minCount 0 ; + sh:name "memberOf" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class org:Organization ] ) ; + sh:path org:memberOf ; + sh:severity sh:Violation . + +dcat-us-shp:Person_Shape-name a sh:PropertyShape ; + rdfs:label "name" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:datatype xsd:string ; + sh:description "The full name of the Person" ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "name" ; + sh:nodeKind sh:Literal ; + sh:path foaf:name ; + sh:severity sh:Violation . + +dcat-us-shp:ProvenanceStatement_Shape-label a sh:PropertyShape ; + rdfs:label "provenance statement text" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property contains the text of the Provenance Statement. This property can be repeated for parallel language versions of the name" ; + sh:minCount 0 ; + sh:name "label" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path rdfs:label ; + sh:severity sh:Violation . + +dcat-us-shp:QualityMeasurement_Shape-isMeasurementOf a sh:PropertyShape ; + rdfs:label "is measurement of" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "Indicates the metric being observed." ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "isMeasurementOf" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dqv:Metric ] ) ; + sh:path dqv:isMeasurementOf ; + sh:severity sh:Violation . + +dcat-us-shp:QualityMeasurement_Shape-unitMeasure a sh:PropertyShape ; + rdfs:label "unit of measure" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "Unit of measure associated with the value" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "unitMeasure" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class rdfs:Resource ] ) ; + sh:path sdmx-attribute:unitMeasure ; + sh:severity sh:Violation . + +dcat-us-shp:QualityMeasurement_Shape-value a sh:PropertyShape ; + rdfs:label "value" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "Refers to values computed by metric." ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "value" ; + sh:nodeKind sh:Literal ; + sh:path dqv:value ; + sh:severity sh:Violation . + +dcat-us-shp:Relationship_Shape-hadRole a sh:PropertyShape ; + rdfs:label "role" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "The function of an entity or agent with respect to another entity or resource." ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "hadRole" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class dcat:Role ] ) ; + sh:path dcat:hadRole ; + sh:severity sh:Violation . + +dcat-us-shp:Relationship_Shape-relation a sh:PropertyShape ; + rdfs:label "relation" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "The resource related to the source resource." ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "relation" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class rdfs:Resource ] ) ; + sh:path dcterms:relation ; + sh:severity sh:Violation . + +dcat-us-shp:RightsStatement_Shape-attributionText a sh:PropertyShape ; + rdfs:label "attribution text" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The custom attribution text for the right statement" ; + sh:minCount 0 ; + sh:name "attributionText" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path odrs:attributionText ; + sh:severity sh:Violation . + +dcat-us-shp:Standard_Shape-category a sh:PropertyShape ; + rdfs:label "category" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property refers to the type of the Standard. A controlled vocabulary for the values has not been established." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "category" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path dcterms:type ; + sh:severity sh:Violation . + +dcat-us-shp:Standard_Shape-created a sh:PropertyShape ; + rdfs:label "creation date" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property contains the date on which the Standard has been first created." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "created" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:created ; + sh:severity sh:Violation . + +dcat-us-shp:Standard_Shape-description a sh:PropertyShape ; + rdfs:label "description" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property contains a free-text account of the Standard. This property can be repeated for parallel language versions of the description" ; + sh:minCount 0 ; + sh:name "description" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:description ; + sh:severity sh:Violation . + +dcat-us-shp:Standard_Shape-identifier a sh:PropertyShape ; + rdfs:label "identifier" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property contains the main identifier for the Standard, e.g. the URI or other unique identifier in the context of the Catalogue, or of a reference register" ; + sh:minCount 0 ; + sh:name "identifier" ; + sh:nodeKind sh:Literal ; + sh:path dcterms:identifier ; + sh:severity sh:Violation . + +dcat-us-shp:Standard_Shape-inScheme a sh:PropertyShape ; + rdfs:label "in scheme" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property MAY be used to specify the reference register to which the Standard belongs." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "inScheme" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:ConceptScheme ] ) ; + sh:path skos:inScheme ; + sh:severity sh:Violation . + +dcat-us-shp:Standard_Shape-issued a sh:PropertyShape ; + rdfs:label "issued" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property contains the date of formal issuance (e.g., publication) of the Standard." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "issued" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:issued ; + sh:severity sh:Violation . + +dcat-us-shp:Standard_Shape-modified a sh:PropertyShape ; + rdfs:label "last modified" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "This property contains the most recent date on which the Standard was changed or modified." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "modified" ; + sh:nodeKind sh:Literal ; + sh:or dcat-us-shp:DateOrDateTimeOrYearOrYearMonth ; + sh:path dcterms:modified ; + sh:severity sh:Violation . + +dcat-us-shp:Standard_Shape-title a sh:PropertyShape ; + rdfs:label "title" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "This property contains a name given to the Standard. This property can be repeated for parallel language versions of the name" ; + sh:minCount 0 ; + sh:name "title" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcterms:title ; + sh:severity sh:Violation . + +dcat-us-shp:UseRestriction_Shape-restrictionNote a sh:PropertyShape ; + rdfs:label "restriction note" ; + dcat-us-shp:requirementLevel dcat-us-shp:Optional ; + sh:description "Significant information pertaining to the use or reproduction of the data." ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "restrictionNote" ; + sh:nodeKind sh:Literal ; + sh:or dash:StringOrLangString ; + sh:path dcat-us:restrictionNote ; + sh:severity sh:Violation . + +dcat-us-shp:UseRestriction_Shape-restrictionStatus a sh:PropertyShape ; + rdfs:label "restriction status" ; + dcat-us-shp:requirementLevel dcat-us-shp:Mandatory ; + sh:description "Indication of whether or not there are use restrictions on the archival materials" ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:name "restrictionStatus" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path dcat-us:restrictionStatus ; + sh:severity sh:Violation . + +dcat-us-shp:UseRestriction_Shape-specificRestriction a sh:PropertyShape ; + rdfs:label "specific restriction" ; + dcat-us-shp:requirementLevel dcat-us-shp:Recommended ; + sh:description "The specific NARA restriction associated with the use restriction" ; + sh:maxCount 1 ; + sh:minCount 0 ; + sh:name "specificRestriction" ; + sh:or ( [ sh:nodeKind sh:IRI ] [ sh:class skos:Concept ] ) ; + sh:path dcat-us:specificRestriction ; + sh:severity sh:Violation . + + a foaf:Person ; + foaf:name "Stephane Fellah" . + +dcat-us-shp:GeometryAsWKTorGMLorGeoJSON a rdf:List ; + rdf:first [ sh:datatype gsp:wktLiteral ] ; + rdf:rest ( [ sh:datatype gsp:gmlLiteral ] [ sh:datatype gsp:geoJSONLiteral ] ) . + +dcat-us-shp:DateOrDateTimeOrYearOrYearMonth a rdf:List ; + rdf:first [ sh:datatype xsd:date ] ; + rdf:rest ( [ sh:datatype xsd:dateTime ] [ sh:datatype xsd:gYear ] [ sh:datatype xsd:gYearMonth ] ) . + +dash:StringOrLangString a rdf:List ; + rdf:first [ sh:datatype xsd:string ] ; + rdf:rest ( [ sh:datatype rdf:langString ] ) . + diff --git a/ckanext/dcat/tests/shacl/test_shacl.py b/ckanext/dcat/tests/shacl/test_shacl.py index 5455ae3e..72db1637 100644 --- a/ckanext/dcat/tests/shacl/test_shacl.py +++ b/ckanext/dcat/tests/shacl/test_shacl.py @@ -199,3 +199,40 @@ def test_validate_dcat_ap_3_graph(): ] assert set(failures) - set(known_failures) == set(), results_text + + +@pytest.mark.usefixtures("with_plugins", "clean_db") +@pytest.mark.ckan_config("ckan.plugins", "dcat scheming_datasets") +@pytest.mark.ckan_config( + "scheming.dataset_schemas", "ckanext.dcat.schemas:dcat_ap_full.yaml" +) +@pytest.mark.ckan_config( + "scheming.presets", + "ckanext.scheming:presets.json ckanext.dcat.schemas:presets.yaml", +) +@pytest.mark.ckan_config("ckanext.dcat.rdf.profiles", "dcat_us_3") +def test_validate_dcat_us_3_graph(): + + graph = graph_from_dataset("ckan_full_dataset_dcat_us_vocabularies.json") + + path = _get_shacl_file_path("dcat-us_3.0_shacl_shapes.ttl") + r = validate(graph, shacl_graph=path) + conforms, results_graph, results_text = r + + failures = [ + str(t[2]) + for t in results_graph.triples( + ( + None, + URIRef("http://www.w3.org/ns/shacl#resultMessage"), + None, + ) + ) + ] + + known_failures = [ + "Value does not have class skos:Concept", + "Value does not have class dcat:Dataset", + ] + + assert set(failures) - set(known_failures) == set(), results_text diff --git a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json new file mode 100644 index 00000000..4ce21a16 --- /dev/null +++ b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json @@ -0,0 +1,189 @@ +{ + "name": "test-dataset-shacl", + "title": "Test DCAT dataset", + "notes": "Lorem ipsum", + "url": "http://example.org/ds1", + "version": "1.0b", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ], + "issued": "2024-05-01", + "modified": "2024-05-05", + "identifier": "xx-some-dataset-id-yy", + "frequency": "http://publications.europa.eu/resource/authority/frequency/MONTHLY", + "provenance": "https://example.org/some-provenance-statement.html", + "dcat_type": "http://purl.org/dc/dcmitype/Dataset", + "version_notes": "Some version notes", + "access_rights": "http://publications.europa.eu/resource/authority/access-right/PUBLIC", + "alternate_identifier": [ + "https://org1.example.org/datasets/alt-id-1", + "https://org2.example.org/datasets/alt-id-2" + ], + "theme": [ + "https://example.org/uri/theme1", + "https://example.org/uri/theme2", + "https://example.org/uri/theme3" + ], + "language": [ + "http://publications.europa.eu/resource/authority/language/ENG", + "http://publications.europa.eu/resource/authority/language/SPA", + "http://publications.europa.eu/resource/authority/language/CAT" + ], + "documentation": [ + "https://example.org/some-doc.html" + ], + "conforms_to": [ + "https://joinup.ec.europa.eu/collection/semic-support-centre/solution/dcat-application-profile-data-portals-europe/release/211", + "https://joinup.ec.europa.eu/collection/semic-support-centre/solution/dcat-application-profile-data-portals-europe/release/210" + ], + "is_referenced_by": [ + "https://doi.org/10.1038/sdata.2018.22" + ], + "applicable_legislation": [ + "http://data.europa.eu/eli/reg_impl/2023/138/oj", + "http://data.europa.eu/eli/reg_impl/2023/138/oj_alt" + ], + "contact": [ + { + "name": "Contact 1", + "email": "contact1@example.org" + }, + { + "name": "Contact 2", + "email": "contact2@example.org" + } + ], + "publisher": [ + { + "name": "Test Publisher", + "email": "publisher@example.org", + "url": "https://example.org", + "type": "http://publications.europa.eu/resource/authority/corporate-body-classification/CB_INT" + } + ], + "temporal_coverage": [ + { + "start": "1905-03-01", + "end": "2013-01-05" + } + ], + "temporal_resolution": "PT15M", + "spatial_coverage": [ + { + "geom": { + "type": "Polygon", + "coordinates": [ + [ + [ + 11.9936, + 54.0486 + ], + [ + 11.9936, + 54.2466 + ], + [ + 12.3045, + 54.2466 + ], + [ + 12.3045, + 54.0486 + ], + [ + 11.9936, + 54.0486 + ] + ] + ] + }, + "text": "Tarragona", + "uri": "https://sws.geonames.org/6361390/", + "bbox": { + "type": "Polygon", + "coordinates": [ + [ + [ + -2.1604, + 42.7611 + ], + [ + -2.0938, + 42.7611 + ], + [ + -2.0938, + 42.7931 + ], + [ + -2.1604, + 42.7931 + ], + [ + -2.1604, + 42.7611 + ] + ] + ] + }, + "centroid": { + "type": "Point", + "coordinates": [ + 1.26639, + 41.12386 + ] + } + } + ], + "spatial_resolution_in_meters": 1.5, + "resources": [ + { + "name": "Resource 1", + "description": "Some description", + "url": "https://example.com/data.csv", + "format": "http://publications.europa.eu/resource/authority/file-type/CSV", + "mimetype": "http://www.iana.org/assignments/media-types/text/csv", + "availability": "http://publications.europa.eu/resource/authority/planned-availability/EXPERIMENTAL", + "compress_format": "http://www.iana.org/assignments/media-types/application/gzip", + "package_format": "http://publications.europa.eu/resource/authority/file-type/TAR", + "size": 12323, + "hash": "4304cf2e751e6053c90b1804c89c0ebb758f395a", + "hash_algorithm": "http://spdx.org/rdf/terms#checksumAlgorithm_sha1", + "status": "http://purl.org/adms/status/Completed", + "access_url": "https://example.com/data.csv", + "download_url": "https://example.com/data.csv", + "issued": "2024-05-01T01:20:33", + "modified": "2024-05-05T09:33:20", + "license": "http://publications.europa.eu/resource/authority/licence/CC_BYNC_4_0", + "rights": "http://publications.europa.eu/resource/authority/access-right/PUBLIC", + "conforms_to": [ + "https://joinup.ec.europa.eu/collection/semic-support-centre/solution/dcat-application-profile-data-portals-europe/release/211", + "https://joinup.ec.europa.eu/collection/semic-support-centre/solution/dcat-application-profile-data-portals-europe/release/210" + ], + "language": [ + "http://publications.europa.eu/resource/authority/language/ENG", + "http://publications.europa.eu/resource/authority/language/SPA", + "http://publications.europa.eu/resource/authority/language/CAT" + ], + "access_services": [ + { + "title": "Access Service 1", + "endpoint_description": "https://example.org/endpoint_description", + "endpoint_url": [ + "https://example.org/access_service/1", + "https://example.org/access_service/2" + ], + "serves_dataset": [ + "https://example.org/dataset/1", + "https://example.org/dataset/2" + ] + } + ] + } + ] +} diff --git a/setup.py b/setup.py index 9a600e06..aa1bf50d 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,7 @@ euro_dcat_ap_2=ckanext.dcat.profiles:EuropeanDCATAP2Profile euro_dcat_ap_3=ckanext.dcat.profiles:EuropeanDCATAP3Profile euro_dcat_ap_scheming=ckanext.dcat.profiles:EuropeanDCATAPSchemingProfile + dcat_us_3=ckanext.dcat.profiles:DCATUS3Profile schemaorg=ckanext.dcat.profiles:SchemaOrgProfile [babel.extractors] From 171a7d8bb6e41c589e59a07b0faa169cfc42686c Mon Sep 17 00:00:00 2001 From: amercader Date: Fri, 4 Oct 2024 15:47:02 +0200 Subject: [PATCH 03/26] Adapt example values to US profile --- ...kan_full_dataset_dcat_us_vocabularies.json | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json index 4ce21a16..08843e0d 100644 --- a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json +++ b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json @@ -15,11 +15,11 @@ "issued": "2024-05-01", "modified": "2024-05-05", "identifier": "xx-some-dataset-id-yy", - "frequency": "http://publications.europa.eu/resource/authority/frequency/MONTHLY", + "frequency": "http://purl.org/cld/terms/annual", "provenance": "https://example.org/some-provenance-statement.html", "dcat_type": "http://purl.org/dc/dcmitype/Dataset", "version_notes": "Some version notes", - "access_rights": "http://publications.europa.eu/resource/authority/access-right/PUBLIC", + "access_rights": "https://resources.data.gov/vocab/access-right/TODO/PUBLIC", "alternate_identifier": [ "https://org1.example.org/datasets/alt-id-1", "https://org2.example.org/datasets/alt-id-2" @@ -30,24 +30,19 @@ "https://example.org/uri/theme3" ], "language": [ - "http://publications.europa.eu/resource/authority/language/ENG", - "http://publications.europa.eu/resource/authority/language/SPA", - "http://publications.europa.eu/resource/authority/language/CAT" + "http://id.loc.gov/vocabulary/iso639-1/en", + "http://id.loc.gov/vocabulary/iso639-1/es", + "http://id.loc.gov/vocabulary/iso639-1/ca" ], "documentation": [ "https://example.org/some-doc.html" ], "conforms_to": [ - "https://joinup.ec.europa.eu/collection/semic-support-centre/solution/dcat-application-profile-data-portals-europe/release/211", - "https://joinup.ec.europa.eu/collection/semic-support-centre/solution/dcat-application-profile-data-portals-europe/release/210" + "https://resources.data.gov/vocab/TODO/ProtocolValue/DCAT_US_3_0" ], "is_referenced_by": [ "https://doi.org/10.1038/sdata.2018.22" ], - "applicable_legislation": [ - "http://data.europa.eu/eli/reg_impl/2023/138/oj", - "http://data.europa.eu/eli/reg_impl/2023/138/oj_alt" - ], "contact": [ { "name": "Contact 1", @@ -62,8 +57,7 @@ { "name": "Test Publisher", "email": "publisher@example.org", - "url": "https://example.org", - "type": "http://publications.europa.eu/resource/authority/corporate-body-classification/CB_INT" + "url": "https://example.org" } ], "temporal_coverage": [ @@ -146,9 +140,8 @@ "name": "Resource 1", "description": "Some description", "url": "https://example.com/data.csv", - "format": "http://publications.europa.eu/resource/authority/file-type/CSV", + "format": "https://resources.data.gov/vocab/file-type/TODO/CSV", "mimetype": "http://www.iana.org/assignments/media-types/text/csv", - "availability": "http://publications.europa.eu/resource/authority/planned-availability/EXPERIMENTAL", "compress_format": "http://www.iana.org/assignments/media-types/application/gzip", "package_format": "http://publications.europa.eu/resource/authority/file-type/TAR", "size": 12323, @@ -159,16 +152,15 @@ "download_url": "https://example.com/data.csv", "issued": "2024-05-01T01:20:33", "modified": "2024-05-05T09:33:20", - "license": "http://publications.europa.eu/resource/authority/licence/CC_BYNC_4_0", - "rights": "http://publications.europa.eu/resource/authority/access-right/PUBLIC", + "license": "https://resources.data.gov/vocab/license/TODO/CC_BYNC_4_0", + "rights": "This dataset is provided by the United States Malacological Commission (USMC)", "conforms_to": [ - "https://joinup.ec.europa.eu/collection/semic-support-centre/solution/dcat-application-profile-data-portals-europe/release/211", - "https://joinup.ec.europa.eu/collection/semic-support-centre/solution/dcat-application-profile-data-portals-europe/release/210" + "https://resources.data.gov/vocab/TODO/ProtocolValue/DCAT_US_3_0" ], "language": [ - "http://publications.europa.eu/resource/authority/language/ENG", - "http://publications.europa.eu/resource/authority/language/SPA", - "http://publications.europa.eu/resource/authority/language/CAT" + "http://id.loc.gov/vocabulary/iso639-1/en", + "http://id.loc.gov/vocabulary/iso639-1/es", + "http://id.loc.gov/vocabulary/iso639-1/ca" ], "access_services": [ { From 4259f19ff4882f01c095de6e301c8c8f9c5ddd70 Mon Sep 17 00:00:00 2001 From: amercader Date: Mon, 7 Oct 2024 12:33:35 +0200 Subject: [PATCH 04/26] Separate schema file for DCAT US --- ckanext/dcat/schemas/dcat_us_full.yaml | 387 +++++++++++++++++++++++++ ckanext/dcat/tests/shacl/test_shacl.py | 5 +- 2 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 ckanext/dcat/schemas/dcat_us_full.yaml diff --git a/ckanext/dcat/schemas/dcat_us_full.yaml b/ckanext/dcat/schemas/dcat_us_full.yaml new file mode 100644 index 00000000..4585bd8c --- /dev/null +++ b/ckanext/dcat/schemas/dcat_us_full.yaml @@ -0,0 +1,387 @@ +scheming_version: 2 +dataset_type: dataset +about: Full DCAT US schema +about_url: http://github.com/ckan/ckanext-dcat + +dataset_fields: + +- field_name: title + label: Title + preset: title + required: true + help_text: A descriptive title for the dataset. + +- field_name: name + label: URL + preset: dataset_slug + form_placeholder: eg. my-dataset + +- field_name: notes + label: Description + required: true + form_snippet: markdown.html + help_text: A free-text account of the dataset. + +- field_name: tag_string + label: Keywords + preset: tag_string_autocomplete + form_placeholder: eg. economy, mental health, government + help_text: Keywords or tags describing the dataset. Use commas to separate multiple values. + +- field_name: contact + label: Contact points + repeating_label: Contact point + repeating_subfields: + + - field_name: uri + label: URI + + - field_name: name + label: Name + + - field_name: email + label: Email + display_snippet: email.html + help_text: Contact information for enquiries about the dataset. + +- field_name: publisher + label: Publisher + repeating_label: Publisher + repeating_once: true + repeating_subfields: + + - field_name: uri + label: URI + + - field_name: name + label: Name + + - field_name: email + label: Email + display_snippet: email.html + + - field_name: url + label: URL + display_snippet: link.html + + - field_name: type + label: Type + + - field_name: identifier + label: Identifier + help_text: Unique identifier for the publisher, such as a ROR ID. + help_text: Entity responsible for making the dataset available. + +- field_name: license_id + label: License + form_snippet: license.html + help_text: License definitions and additional information can be found at http://opendefinition.org/. + +- field_name: owner_org + label: Organization + preset: dataset_organization + help_text: The CKAN organization the dataset belongs to. + +- field_name: url + label: Landing page + form_placeholder: http://example.com/dataset.json + display_snippet: link.html + help_text: Web page that can be navigated to gain access to the dataset, its distributions and/or additional information. + + # Note: this will fall back to metadata_created if not present +- field_name: issued + label: Release date + preset: dcat_date + help_text: Date of publication of the dataset. + + # Note: this will fall back to metadata_modified if not present +- field_name: modified + label: Modification date + preset: dcat_date + help_text: Most recent date on which the dataset was changed, updated or modified. + +- field_name: version + label: Version + validators: ignore_missing unicode_safe package_version_validator + help_text: Version number or other version designation of the dataset. + +- field_name: version_notes + label: Version notes + validators: ignore_missing unicode_safe + form_snippet: markdown.html + display_snippet: markdown.html + help_text: A description of the differences between this version and a previous version of the dataset. + + # Note: CKAN will generate a unique identifier for each dataset +- field_name: identifier + label: Identifier + help_text: A unique identifier of the dataset. + +- field_name: frequency + label: Frequency + help_text: The frequency at which dataset is published. + +- field_name: provenance + label: Provenance + form_snippet: markdown.html + display_snippet: markdown.html + help_text: A statement about the lineage of the dataset. + +- field_name: dcat_type + label: Category + help_text: The type of the dataset. + # TODO: controlled vocabulary? + +- field_name: temporal_coverage + label: Temporal coverage + repeating_subfields: + + - field_name: start + label: Start + preset: dcat_date + + - field_name: end + label: End + preset: dcat_date + help_text: The temporal period or periods the dataset covers. + +- field_name: temporal_resolution + label: Temporal resolution + help_text: Minimum time period resolvable in the dataset. + +- field_name: spatial_coverage + label: Spatial coverage + repeating_subfields: + + - field_name: uri + label: URI + + - field_name: text + label: Label + + - field_name: geom + label: Geometry + + - field_name: bbox + label: Bounding Box + + - field_name: centroid + label: Centroid + help_text: A geographic region that is covered by the dataset. + +- field_name: spatial_resolution_in_meters + label: Spatial resolution in meters + help_text: Minimum spatial separation resolvable in a dataset, measured in meters. + +- field_name: access_rights + label: Access rights + validators: ignore_missing unicode_safe + help_text: Information that indicates whether the dataset is Open Data, has access restrictions or is not public. + +- field_name: alternate_identifier + label: Other identifier + preset: multiple_text + validators: ignore_missing scheming_multiple_text + help_text: This property refers to a secondary identifier of the dataset, such as MAST/ADS, DataCite, DOI, etc. + +- field_name: theme + label: Theme + preset: multiple_text + validators: ignore_missing scheming_multiple_text + help_text: A category of the dataset. A Dataset may be associated with multiple themes. + +- field_name: language + label: Language + preset: multiple_text + validators: ignore_missing scheming_multiple_text + help_text: Language or languages of the dataset. + # TODO: language form snippet / validator / graph + +- field_name: documentation + label: Documentation + preset: multiple_text + validators: ignore_missing scheming_multiple_text + help_text: A page or document about this dataset. + +- field_name: conforms_to + label: Conforms to + preset: multiple_text + validators: ignore_missing scheming_multiple_text + help_text: An implementing rule or other specification that the dataset follows. + +- field_name: is_referenced_by + label: Is referenced by + preset: multiple_text + validators: ignore_missing scheming_multiple_text + help_text: A related resource, such as a publication, that references, cites, or otherwise points to the dataset. + +- field_name: applicable_legislation + label: Applicable legislation + preset: multiple_text + validators: ignore_missing scheming_multiple_text + help_text: The legislation that mandates the creation or management of the dataset. + +#- field_name: hvd_category +# label: HVD Category +# preset: multiple_text +# validators: ignore_missing scheming_multiple_text +# TODO: implement separately as part of wider HVD support + +# Note: if not provided, this will be autogenerated +- field_name: uri + label: URI + help_text: An URI for this dataset (if not provided it will be autogenerated). + +# TODO: relation-based properties are not yet included (e.g. is_version_of, source, sample, etc) +# +resource_fields: + +- field_name: url + label: URL + preset: resource_url_upload + +- field_name: name + label: Name + form_placeholder: + help_text: A descriptive title for the resource. + +- field_name: description + label: Description + form_snippet: markdown.html + help_text: A free-text account of the resource. + +- field_name: format + label: Format + preset: resource_format_autocomplete + help_text: File format. If not provided it will be guessed. + +- field_name: mimetype + label: Media type + validators: if_empty_guess_format ignore_missing unicode_safe + help_text: Media type for this format. If not provided it will be guessed. + +- field_name: compress_format + label: Compress format + help_text: The format of the file in which the data is contained in a compressed form. + +- field_name: package_format + label: Package format + help_text: The format of the file in which one or more data files are grouped together. + +- field_name: size + label: Size + validators: ignore_missing int_validator + form_snippet: number.html + display_snippet: file_size.html + help_text: File size in bytes + +- field_name: hash + label: Hash + help_text: Checksum of the downloaded file. + +- field_name: hash_algorithm + label: Hash Algorithm + help_text: Algorithm used to calculate to checksum. + +- field_name: rights + label: Rights + form_snippet: markdown.html + display_snippet: markdown.html + help_text: Some statement about the rights associated with the resource. + +- field_name: availability + label: Availability + help_text: Indicates how long it is planned to keep the resource available. + +- field_name: status + label: Status + preset: select + choices: + - value: http://purl.org/adms/status/Completed + label: Completed + - value: http://purl.org/adms/status/UnderDevelopment + label: Under Development + - value: http://purl.org/adms/status/Deprecated + label: Deprecated + - value: http://purl.org/adms/status/Withdrawn + label: Withdrawn + help_text: The status of the resource in the context of maturity lifecycle. + +- field_name: license + label: License + help_text: License in which the resource is made available. If not provided will be inherited from the dataset. + + # Note: this falls back to the standard resource url field +- field_name: access_url + label: Access URL + help_text: URL that gives access to the dataset (defaults to the standard resource URL). + + # Note: this falls back to the standard resource url field +- field_name: download_url + label: Download URL + display_snippet: link.html + help_text: URL that provides a direct link to a downloadable file (defaults to the standard resource URL). + +- field_name: issued + label: Release date + preset: dcat_date + help_text: Date of publication of the resource. + +- field_name: modified + label: Modification date + preset: dcat_date + help_text: Most recent date on which the resource was changed, updated or modified. + +- field_name: language + label: Language + preset: multiple_text + validators: ignore_missing scheming_multiple_text + help_text: Language or languages of the resource. + +- field_name: documentation + label: Documentation + preset: multiple_text + validators: ignore_missing scheming_multiple_text + help_text: A page or document about this resource. + +- field_name: conforms_to + label: Conforms to + preset: multiple_text + validators: ignore_missing scheming_multiple_text + help_text: An established schema to which the described resource conforms. + +- field_name: applicable_legislation + label: Applicable legislation + preset: multiple_text + validators: ignore_missing scheming_multiple_text + help_text: The legislation that mandates the creation or management of the resource. + +- field_name: access_services + label: Access services + repeating_label: Access service + repeating_subfields: + + - field_name: uri + label: URI + + - field_name: title + label: Title + + - field_name: endpoint_description + label: Endpoint description + + - field_name: endpoint_url + label: Endpoint URL + preset: multiple_text + + - field_name: serves_dataset + label: Serves dataset + preset: multiple_text + validators: ignore_missing scheming_multiple_text + + help_text: A data service that gives access to the resource. + + # Note: if not provided, this will be autogenerated +- field_name: uri + label: URI + help_text: An URI for this resource (if not provided it will be autogenerated). diff --git a/ckanext/dcat/tests/shacl/test_shacl.py b/ckanext/dcat/tests/shacl/test_shacl.py index 72db1637..dadf0071 100644 --- a/ckanext/dcat/tests/shacl/test_shacl.py +++ b/ckanext/dcat/tests/shacl/test_shacl.py @@ -204,7 +204,7 @@ def test_validate_dcat_ap_3_graph(): @pytest.mark.usefixtures("with_plugins", "clean_db") @pytest.mark.ckan_config("ckan.plugins", "dcat scheming_datasets") @pytest.mark.ckan_config( - "scheming.dataset_schemas", "ckanext.dcat.schemas:dcat_ap_full.yaml" + "scheming.dataset_schemas", "ckanext.dcat.schemas:dcat_us_full.yaml" ) @pytest.mark.ckan_config( "scheming.presets", @@ -219,6 +219,9 @@ def test_validate_dcat_us_3_graph(): r = validate(graph, shacl_graph=path) conforms, results_graph, results_text = r + with open("shacl.txt", "w") as f: + f.write(results_text) + failures = [ str(t[2]) for t in results_graph.triples( From e9bb1b32623dc92dd64293d56882a0ad0a8f6906 Mon Sep 17 00:00:00 2001 From: amercader Date: Mon, 7 Oct 2024 13:25:01 +0200 Subject: [PATCH 05/26] Consolidate input datasets in tests Use the same files in examples used in the shacl tests, don't use separate dataset dicts on each test. --- .../dcat_ap_2/test_scheming_support.py | 130 +----------------- .../test_euro_dcatap_3_profile_serialize.py | 121 +--------------- ckanext/dcat/tests/utils.py | 3 + examples/ckan/ckan_full_dataset_dcat_ap.json | 15 ++ 4 files changed, 25 insertions(+), 244 deletions(-) diff --git a/ckanext/dcat/tests/profiles/dcat_ap_2/test_scheming_support.py b/ckanext/dcat/tests/profiles/dcat_ap_2/test_scheming_support.py index 8095202f..65b2c074 100644 --- a/ckanext/dcat/tests/profiles/dcat_ap_2/test_scheming_support.py +++ b/ckanext/dcat/tests/profiles/dcat_ap_2/test_scheming_support.py @@ -48,133 +48,9 @@ def test_e2e_ckan_to_dcat(self): are exposed in the DCAT RDF graph """ - dataset_dict = { - # Core fields - "name": "test-dataset", - "title": "Test DCAT dataset", - "notes": "Lorem ipsum", - "url": "http://example.org/ds1", - "version": "1.0b", - "tags": [{"name": "Tag 1"}, {"name": "Tag 2"}], - # Standard fields - "issued": "2024-05-01", - "modified": "2024-05-05", - "identifier": "xx-some-dataset-id-yy", - "frequency": "monthly", - "provenance": "Statement about provenance", - "dcat_type": "test-type", - "version_notes": "Some version notes", - "access_rights": "Statement about access rights", - # List fields (lists) - "alternate_identifier": ["alt-id-1", "alt-id-2"], - "theme": [ - "https://example.org/uri/theme1", - "https://example.org/uri/theme2", - "https://example.org/uri/theme3", - ], - "language": ["en", "ca", "es"], - "documentation": ["https://example.org/some-doc.html"], - "conforms_to": ["Standard 1", "Standard 2"], - "is_referenced_by": [ - "https://doi.org/10.1038/sdata.2018.22", - "test_isreferencedby", - ], - "applicable_legislation": [ - "http://data.europa.eu/eli/reg_impl/2023/138/oj", - "http://data.europa.eu/eli/reg_impl/2023/138/oj_alt", - ], - # Repeating subfields - "contact": [ - {"name": "Contact 1", "email": "contact1@example.org"}, - {"name": "Contact 2", "email": "contact2@example.org"}, - ], - "publisher": [ - { - "name": "Test Publisher", - "email": "publisher@example.org", - "url": "https://example.org", - "type": "public_body", - "identifier": "http://example.org/publisher-id", - }, - ], - "creator": [ - { - "name": "Test Creator", - "email": "creator@example.org", - "url": "https://example.org/creator", - "type": "person", - "identifier": "http://example.org/creator-id", - } - ], - "temporal_coverage": [ - {"start": "1905-03-01", "end": "2013-01-05"}, - {"start": "2024-04-10", "end": "2024-05-29"}, - ], - "temporal_resolution": "PT15M", - "spatial_coverage": [ - { - "geom": { - "type": "Polygon", - "coordinates": [ - [ - [11.9936, 54.0486], - [11.9936, 54.2466], - [12.3045, 54.2466], - [12.3045, 54.0486], - [11.9936, 54.0486], - ] - ], - }, - "text": "Tarragona", - "uri": "https://sws.geonames.org/6361390/", - "bbox": { - "type": "Polygon", - "coordinates": [ - [ - [-2.1604, 42.7611], - [-2.0938, 42.7611], - [-2.0938, 42.7931], - [-2.1604, 42.7931], - [-2.1604, 42.7611], - ] - ], - }, - "centroid": {"type": "Point", "coordinates": [1.26639, 41.12386]}, - } - ], - "spatial_resolution_in_meters": 1.5, - "resources": [ - { - "name": "Resource 1", - "description": "Some description", - "url": "https://example.com/data.csv", - "format": "CSV", - "availability": "http://publications.europa.eu/resource/authority/planned-availability/EXPERIMENTAL", - "compress_format": "http://www.iana.org/assignments/media-types/application/gzip", - "package_format": "http://publications.europa.eu/resource/authority/file-type/TAR", - "size": 12323, - "hash": "4304cf2e751e6053c90b1804c89c0ebb758f395a", - "hash_algorithm": "http://spdx.org/rdf/terms#checksumAlgorithm_sha1", - "status": "http://purl.org/adms/status/Completed", - "access_url": "https://example.com/data.csv", - "download_url": "https://example.com/data.csv", - "issued": "2024-05-01T01:20:33", - "modified": "2024-05-05T09:33:20", - "license": "http://creativecommons.org/licenses/by/3.0/", - "rights": "Some stament about rights", - "language": ["en", "ca", "es"], - "access_services": [ - { - "title": "Access Service 1", - "endpoint_url": [ - "https://example.org/access_service/1", - "https://example.org/access_service/2", - ], - } - ], - } - ], - } + dataset_dict = json.loads( + self._get_file_contents("ckan/ckan_full_dataset_dcat_ap.json") + ) dataset = call_action("package_create", **dataset_dict) diff --git a/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_serialize.py b/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_serialize.py index e0001526..893ce1d4 100644 --- a/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_serialize.py +++ b/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_serialize.py @@ -1,3 +1,4 @@ +import json import pytest from rdflib.namespace import RDF @@ -45,123 +46,9 @@ def test_e2e_ckan_to_dcat(self): are exposed in the DCAT RDF graph """ - dataset_dict = { - # Core fields - "name": "test-dataset", - "title": "Test DCAT dataset", - "notes": "Lorem ipsum", - "url": "http://example.org/ds1", - "version": "1.0b", - "tags": [{"name": "Tag 1"}, {"name": "Tag 2"}], - # Standard fields - "issued": "2024-05-01", - "modified": "2024-05-05", - "identifier": "xx-some-dataset-id-yy", - "frequency": "monthly", - "provenance": "Statement about provenance", - "dcat_type": "test-type", - "version_notes": "Some version notes", - "access_rights": "Statement about access rights", - # List fields (lists) - "alternate_identifier": ["alt-id-1", "alt-id-2"], - "theme": [ - "https://example.org/uri/theme1", - "https://example.org/uri/theme2", - "https://example.org/uri/theme3", - ], - "language": ["en", "ca", "es"], - "documentation": ["https://example.org/some-doc.html"], - "conforms_to": ["Standard 1", "Standard 2"], - "is_referenced_by": [ - "https://doi.org/10.1038/sdata.2018.22", - "test_isreferencedby", - ], - "applicable_legislation": [ - "http://data.europa.eu/eli/reg_impl/2023/138/oj", - "http://data.europa.eu/eli/reg_impl/2023/138/oj_alt", - ], - # Repeating subfields - "contact": [ - {"name": "Contact 1", "email": "contact1@example.org"}, - {"name": "Contact 2", "email": "contact2@example.org"}, - ], - "publisher": [ - { - "name": "Test Publisher", - "email": "publisher@example.org", - "url": "https://example.org", - "type": "public_body", - }, - ], - "temporal_coverage": [ - {"start": "1905-03-01", "end": "2013-01-05"}, - {"start": "2024-04-10", "end": "2024-05-29"}, - ], - "temporal_resolution": "PT15M", - "spatial_coverage": [ - { - "geom": { - "type": "Polygon", - "coordinates": [ - [ - [11.9936, 54.0486], - [11.9936, 54.2466], - [12.3045, 54.2466], - [12.3045, 54.0486], - [11.9936, 54.0486], - ] - ], - }, - "text": "Tarragona", - "uri": "https://sws.geonames.org/6361390/", - "bbox": { - "type": "Polygon", - "coordinates": [ - [ - [-2.1604, 42.7611], - [-2.0938, 42.7611], - [-2.0938, 42.7931], - [-2.1604, 42.7931], - [-2.1604, 42.7611], - ] - ], - }, - "centroid": {"type": "Point", "coordinates": [1.26639, 41.12386]}, - } - ], - "spatial_resolution_in_meters": 1.5, - "resources": [ - { - "name": "Resource 1", - "description": "Some description", - "url": "https://example.com/data.csv", - "format": "CSV", - "availability": "http://publications.europa.eu/resource/authority/planned-availability/EXPERIMENTAL", - "compress_format": "http://www.iana.org/assignments/media-types/application/gzip", - "package_format": "http://publications.europa.eu/resource/authority/file-type/TAR", - "size": 12323, - "hash": "4304cf2e751e6053c90b1804c89c0ebb758f395a", - "hash_algorithm": "http://spdx.org/rdf/terms#checksumAlgorithm_sha1", - "status": "http://purl.org/adms/status/Completed", - "access_url": "https://example.com/data.csv", - "download_url": "https://example.com/data.csv", - "issued": "2024-05-01T01:20:33", - "modified": "2024-05-05T09:33:20", - "license": "http://creativecommons.org/licenses/by/3.0/", - "rights": "Some stament about rights", - "language": ["en", "ca", "es"], - "access_services": [ - { - "title": "Access Service 1", - "endpoint_url": [ - "https://example.org/access_service/1", - "https://example.org/access_service/2", - ], - } - ], - } - ], - } + dataset_dict = json.loads( + self._get_file_contents("ckan/ckan_full_dataset_dcat_ap.json") + ) dataset = call_action("package_create", **dataset_dict) diff --git a/ckanext/dcat/tests/utils.py b/ckanext/dcat/tests/utils.py index d314d938..5a3d07cb 100644 --- a/ckanext/dcat/tests/utils.py +++ b/ckanext/dcat/tests/utils.py @@ -64,3 +64,6 @@ def _get_dict_from_list(self, dict_list, key, value): if dict.get(key) == value: return dict return None + + def _get_file_contents(self, file_name): + return get_file_contents(file_name) diff --git a/examples/ckan/ckan_full_dataset_dcat_ap.json b/examples/ckan/ckan_full_dataset_dcat_ap.json index 381804a1..f6c7c636 100644 --- a/examples/ckan/ckan_full_dataset_dcat_ap.json +++ b/examples/ckan/ckan_full_dataset_dcat_ap.json @@ -59,18 +59,33 @@ "email": "contact2@example.org" } ], + "creator": [ + { + "name": "Test Creator", + "email": "creator@example.org", + "url": "https://example.org", + "identifier": "http://example.org/creator-id", + "type": "public_body" + } + ], "publisher": [ { "name": "Test Publisher", "email": "publisher@example.org", "url": "https://example.org", + "identifier": "http://example.org/publisher-id", "type": "public_body" } ], "temporal_coverage": [ { + "start": "1905-03-01", "end": "2013-01-05" + }, + { + "start": "2024-04-10", + "end": "2024-05-29" } ], "temporal_resolution": "PT15M", From 3d8eccb5cd7270237fcf65a124a3aa0ba68f608e Mon Sep 17 00:00:00 2001 From: amercader Date: Mon, 7 Oct 2024 13:40:34 +0200 Subject: [PATCH 06/26] Add tests for dcat_us_3 profile For the parsing ones we are using the existing dataset.rdf file which has a DCAT AP flavour. We can replace it with a US centric if we can come up with a good existing example. --- .../dcat_us_3/test_dcat_us_3_profile_parse.py | 152 ++++++++ .../test_dcat_us_3_profile_serialize.py | 334 ++++++++++++++++++ ...kan_full_dataset_dcat_ap_vocabularies.json | 7 + ...kan_full_dataset_dcat_us_vocabularies.json | 12 + 4 files changed, 505 insertions(+) create mode 100644 ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py create mode 100644 ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py new file mode 100644 index 00000000..da5f4842 --- /dev/null +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py @@ -0,0 +1,152 @@ +import pytest + +from ckan.tests.helpers import call_action + +from ckanext.dcat.processors import RDFParser +from ckanext.dcat.tests.utils import BaseParseTest + + +@pytest.mark.usefixtures("with_plugins", "clean_db") +@pytest.mark.ckan_config("ckan.plugins", "dcat scheming_datasets") +@pytest.mark.ckan_config( + "scheming.dataset_schemas", "ckanext.dcat.schemas:dcat_us_full.yaml" +) +@pytest.mark.ckan_config( + "scheming.presets", + "ckanext.scheming:presets.json ckanext.dcat.schemas:presets.yaml", +) +@pytest.mark.ckan_config("ckanext.dcat.rdf.profiles", "dcat_us_3") +class TestSchemingParseSupport(BaseParseTest): + def test_e2e_dcat_to_ckan(self): + """ + Parse a DCAT RDF graph into a CKAN dataset dict, create a dataset with + package_create and check that all expected fields are there + """ + contents = self._get_file_contents("dcat/dataset.rdf") + + p = RDFParser() + + p.parse(contents) + + datasets = [d for d in p.datasets()] + + assert len(datasets) == 1 + + dataset_dict = datasets[0] + + dataset_dict["name"] = "test-dcat-1" + dataset = call_action("package_create", **dataset_dict) + + # Core fields + + assert dataset["title"] == "Zimbabwe Regional Geochemical Survey." + assert ( + dataset["notes"] + == "During the period 1982-86 a team of geologists from the British Geological Survey ..." + ) + assert dataset["url"] == "http://dataset.info.org" + assert dataset["version"] == "2.3" + assert dataset["license_id"] == "cc-nc" + assert sorted([t["name"] for t in dataset["tags"]]) == [ + "exploration", + "geochemistry", + "geology", + ] + + # Standard fields + assert dataset["version_notes"] == "New schema added" + assert dataset["identifier"] == "9df8df51-63db-37a8-e044-0003ba9b0d98" + assert dataset["frequency"] == "http://purl.org/cld/freq/daily" + assert dataset["access_rights"] == "public" + assert dataset["provenance"] == "Some statement about provenance" + assert dataset["dcat_type"] == "test-type" + + assert dataset["issued"] == "2012-05-10" + assert dataset["modified"] == "2012-05-10T21:04:00" + assert dataset["temporal_resolution"] == "PT15M" + assert dataset["spatial_resolution_in_meters"] == "1.5" + + # List fields + assert sorted(dataset["conforms_to"]) == ["Standard 1", "Standard 2"] + assert sorted(dataset["language"]) == ["ca", "en", "es"] + assert sorted(dataset["theme"]) == [ + "Earth Sciences", + "http://eurovoc.europa.eu/100142", + "http://eurovoc.europa.eu/209065", + ] + assert sorted(dataset["alternate_identifier"]) == [ + "alternate-identifier-1", + "alternate-identifier-2", + ] + assert sorted(dataset["documentation"]) == [ + "http://dataset.info.org/doc1", + "http://dataset.info.org/doc2", + ] + + assert sorted(dataset["is_referenced_by"]) == [ + "https://doi.org/10.1038/sdata.2018.22", + "test_isreferencedby", + ] + + # Repeating subfields + + assert dataset["contact"][0]["name"] == "Point of Contact" + assert dataset["contact"][0]["email"] == "contact@some.org" + + assert ( + dataset["publisher"][0]["name"] == "Publishing Organization for dataset 1" + ) + assert dataset["publisher"][0]["email"] == "contact@some.org" + assert dataset["publisher"][0]["url"] == "http://some.org" + assert ( + dataset["publisher"][0]["type"] + == "http://purl.org/adms/publishertype/NonProfitOrganisation" + ) + assert dataset["temporal_coverage"][0]["start"] == "1905-03-01" + assert dataset["temporal_coverage"][0]["end"] == "2013-01-05" + + resource = dataset["resources"][0] + + # Resources: core fields + assert resource["url"] == "http://www.bgs.ac.uk/gbase/geochemcd/home.html" + + # Resources: standard fields + assert resource["license"] == "http://creativecommons.org/licenses/by-nc/2.0/" + assert resource["rights"] == "Some statement about rights" + assert resource["issued"] == "2012-05-11" + assert resource["modified"] == "2012-05-01T00:04:06" + assert resource["status"] == "http://purl.org/adms/status/Completed" + assert resource["size"] == 12323 + assert ( + resource["compress_format"] + == "http://www.iana.org/assignments/media-types/application/gzip" + ) + assert ( + resource["package_format"] + == "http://publications.europa.eu/resource/authority/file-type/TAR" + ) + + assert resource["hash"] == "4304cf2e751e6053c90b1804c89c0ebb758f395a" + assert ( + resource["hash_algorithm"] + == "http://spdx.org/rdf/terms#checksumAlgorithm_sha1" + ) + + assert ( + resource["access_url"] == "http://www.bgs.ac.uk/gbase/geochemcd/home.html" + ) + assert "download_url" not in resource + + # Resources: list fields + assert sorted(resource["language"]) == ["ca", "en", "es"] + assert sorted(resource["documentation"]) == [ + "http://dataset.info.org/distribution1/doc1", + "http://dataset.info.org/distribution1/doc2", + ] + assert sorted(resource["conforms_to"]) == ["Standard 1", "Standard 2"] + + # Resources: repeating subfields + assert resource["access_services"][0]["title"] == "Sparql-end Point" + assert resource["access_services"][0]["endpoint_url"] == [ + "http://publications.europa.eu/webapi/rdf/sparql" + ] diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py new file mode 100644 index 00000000..2b1a3363 --- /dev/null +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py @@ -0,0 +1,334 @@ +import json +import pytest + +from rdflib.namespace import RDF +from rdflib.term import URIRef +from geomet import wkt + +from ckan.tests.helpers import call_action +from ckanext.dcat.processors import RDFSerializer +from ckanext.dcat.tests.utils import BaseSerializeTest + +from ckanext.dcat import utils +from ckanext.dcat.profiles import ( + RDF, + DCAT, + DCATAP, + DCT, + ADMS, + XSD, + VCARD, + FOAF, + SKOS, + LOCN, + GSP, + OWL, + SPDX, +) + +DCAT_AP_PROFILES = ["dcat_us_3"] + + +class TestDCATUS3ProfileSerializeDataset(BaseSerializeTest): + @pytest.mark.usefixtures("with_plugins", "clean_db") + @pytest.mark.ckan_config("ckan.plugins", "dcat scheming_datasets") + @pytest.mark.ckan_config( + "scheming.dataset_schemas", "ckanext.dcat.schemas:dcat_us_full.yaml" + ) + @pytest.mark.ckan_config( + "scheming.presets", + "ckanext.scheming:presets.json ckanext.dcat.schemas:presets.yaml", + ) + @pytest.mark.ckan_config("ckanext.dcat.rdf.profiles", "dcat_us_3") + def test_e2e_ckan_to_dcat(self): + """ + Create a dataset using the scheming schema, check that fields + are exposed in the DCAT RDF graph + """ + + dataset_dict = json.loads( + self._get_file_contents("ckan/ckan_full_dataset_dcat_us_vocabularies.json") + ) + + dataset = call_action("package_create", **dataset_dict) + + # Make sure schema was used + assert ( + dataset["conforms_to"][0] + == "https://resources.data.gov/vocab/TODO/ProtocolValue/DCAT_US_3_0" + ) + assert dataset["contact"][0]["name"] == "Contact 1" + + s = RDFSerializer() + g = s.g + + dataset_ref = s.graph_from_dataset(dataset) + + assert str(dataset_ref) == utils.dataset_uri(dataset) + + # Core fields + assert self._triple(g, dataset_ref, RDF.type, DCAT.Dataset) + assert self._triple(g, dataset_ref, DCT.title, dataset["title"]) + assert self._triple(g, dataset_ref, DCT.description, dataset["notes"]) + assert self._triple(g, dataset_ref, OWL.versionInfo, dataset["version"]) + + # Standard fields + assert self._triple(g, dataset_ref, DCT.identifier, dataset["identifier"]) + assert self._triple( + g, dataset_ref, DCT.accrualPeriodicity, URIRef(dataset["frequency"]) + ) + assert self._triple( + g, dataset_ref, DCT.provenance, URIRef(dataset["provenance"]) + ) + assert self._triple(g, dataset_ref, DCT.type, URIRef(dataset["dcat_type"])) + assert self._triple(g, dataset_ref, ADMS.versionNotes, dataset["version_notes"]) + assert self._triple(g, dataset_ref, DCT.accessRights, URIRef(dataset["access_rights"])) + assert self._triple( + g, + dataset_ref, + DCAT.spatialResolutionInMeters, + dataset["spatial_resolution_in_meters"], + data_type=XSD.decimal, + ) + + # Dates + assert self._triple( + g, + dataset_ref, + DCT.issued, + dataset["issued"], + data_type=XSD.date, + ) + assert self._triple( + g, + dataset_ref, + DCT.modified, + dataset["modified"], + data_type=XSD.date, + ) + assert self._triple( + g, + dataset_ref, + DCAT.temporalResolution, + dataset["temporal_resolution"], + data_type=XSD.duration, + ) + + # List fields + + assert ( + self._triples_list_values(g, dataset_ref, DCT.conformsTo) + == dataset["conforms_to"] + ) + assert self._triples_list_values(g, dataset_ref, DCAT.theme) == dataset["theme"] + assert ( + self._triples_list_values(g, dataset_ref, DCT.language) + == dataset["language"] + ) + assert ( + self._triples_list_values(g, dataset_ref, FOAF.page) + == dataset["documentation"] + ) + assert ( + self._triples_list_values(g, dataset_ref, DCT.isReferencedBy) + == dataset["is_referenced_by"] + ) + + # Repeating subfields + + contact_details = [t for t in g.triples((dataset_ref, DCAT.contactPoint, None))] + + assert len(contact_details) == len(dataset["contact"]) + assert self._triple( + g, contact_details[0][2], VCARD.fn, dataset_dict["contact"][0]["name"] + ) + assert self._triple( + g, + contact_details[0][2], + VCARD.hasEmail, + URIRef("mailto:" + dataset_dict["contact"][0]["email"]), + ) + assert self._triple( + g, contact_details[1][2], VCARD.fn, dataset_dict["contact"][1]["name"] + ) + assert self._triple( + g, + contact_details[1][2], + VCARD.hasEmail, + URIRef("mailto:" + dataset_dict["contact"][1]["email"]), + ) + + publisher = [t for t in g.triples((dataset_ref, DCT.publisher, None))] + + assert len(publisher) == 1 + assert self._triple( + g, publisher[0][2], FOAF.name, dataset_dict["publisher"][0]["name"] + ) + assert self._triple( + g, + publisher[0][2], + VCARD.hasEmail, + URIRef("mailto:" + dataset_dict["publisher"][0]["email"]), + ) + assert self._triple( + g, + publisher[0][2], + FOAF.homepage, + URIRef(dataset_dict["publisher"][0]["url"]), + ) + + temporal = [t for t in g.triples((dataset_ref, DCT.temporal, None))] + + assert len(temporal) == len(dataset["temporal_coverage"]) + assert self._triple( + g, + temporal[0][2], + DCAT.startDate, + dataset_dict["temporal_coverage"][0]["start"], + data_type=XSD.date, + ) + assert self._triple( + g, + temporal[0][2], + DCAT.endDate, + dataset_dict["temporal_coverage"][0]["end"], + data_type=XSD.date, + ) + assert self._triple( + g, + temporal[1][2], + DCAT.startDate, + dataset_dict["temporal_coverage"][1]["start"], + data_type=XSD.date, + ) + assert self._triple( + g, + temporal[1][2], + DCAT.endDate, + dataset_dict["temporal_coverage"][1]["end"], + data_type=XSD.date, + ) + + spatial = [t for t in g.triples((dataset_ref, DCT.spatial, None))] + assert len(spatial) == len(dataset["spatial_coverage"]) + assert str(spatial[0][2]) == dataset["spatial_coverage"][0]["uri"] + assert self._triple(g, spatial[0][2], RDF.type, DCT.Location) + assert self._triple( + g, spatial[0][2], SKOS.prefLabel, dataset["spatial_coverage"][0]["text"] + ) + + assert len([t for t in g.triples((spatial[0][2], LOCN.Geometry, None))]) == 1 + # Geometry in WKT + wkt_geom = wkt.dumps(dataset["spatial_coverage"][0]["geom"], decimals=4) + assert self._triple(g, spatial[0][2], LOCN.Geometry, wkt_geom, GSP.wktLiteral) + + distribution_ref = self._triple(g, dataset_ref, DCAT.distribution, None)[2] + resource = dataset_dict["resources"][0] + + # Alternate identifiers + ids = [] + for subject in [t[2] for t in g.triples((dataset_ref, ADMS.identifier, None))]: + ids.append(str(g.value(subject, SKOS.notation))) + assert ids == dataset["alternate_identifier"] + + # Resources: core fields + + assert self._triple(g, distribution_ref, DCT.title, resource["name"]) + assert self._triple( + g, + distribution_ref, + DCT.description, + resource["description"], + ) + + # Resources: standard fields + + assert self._triple(g, distribution_ref, DCT.rights, resource["rights"]) + assert self._triple( + g, distribution_ref, ADMS.status, URIRef(resource["status"]) + ) + assert self._triple( + g, + distribution_ref, + DCAT.accessURL, + URIRef(resource["access_url"]), + ) + assert self._triple( + g, + distribution_ref, + DCAT.compressFormat, + URIRef(resource["compress_format"]), + ) + assert self._triple( + g, + distribution_ref, + DCAT.packageFormat, + URIRef(resource["package_format"]), + ) + assert self._triple( + g, + distribution_ref, + DCAT.downloadURL, + URIRef(resource["download_url"]), + ) + + assert self._triple( + g, distribution_ref, DCAT.byteSize, resource["size"], XSD.nonNegativeInteger + ) + + # Checksum + checksum = self._triple(g, distribution_ref, SPDX.checksum, None)[2] + assert checksum + assert self._triple(g, checksum, RDF.type, SPDX.Checksum) + assert self._triple( + g, + checksum, + SPDX.checksumValue, + resource["hash"], + data_type="http://www.w3.org/2001/XMLSchema#hexBinary", + ) + assert self._triple( + g, checksum, SPDX.algorithm, URIRef(resource["hash_algorithm"]) + ) + + # Resources: dates + assert self._triple( + g, + distribution_ref, + DCT.issued, + dataset["resources"][0]["issued"], + data_type=XSD.dateTime, + ) + assert self._triple( + g, + distribution_ref, + DCT.modified, + dataset["resources"][0]["modified"], + data_type=XSD.dateTime, + ) + + # Resources: list fields + assert ( + self._triples_list_values(g, distribution_ref, DCT.language) + == resource["language"] + ) + + # Resource: repeating subfields + access_services = [ + t for t in g.triples((distribution_ref, DCAT.accessService, None)) + ] + + assert len(access_services) == len(dataset["resources"][0]["access_services"]) + assert self._triple( + g, + access_services[0][2], + DCT.title, + resource["access_services"][0]["title"], + ) + + endpoint_urls = [ + str(t[2]) + for t in g.triples((access_services[0][2], DCAT.endpointURL, None)) + ] + assert endpoint_urls == resource["access_services"][0]["endpoint_url"] + diff --git a/examples/ckan/ckan_full_dataset_dcat_ap_vocabularies.json b/examples/ckan/ckan_full_dataset_dcat_ap_vocabularies.json index 4ce21a16..542d1b82 100644 --- a/examples/ckan/ckan_full_dataset_dcat_ap_vocabularies.json +++ b/examples/ckan/ckan_full_dataset_dcat_ap_vocabularies.json @@ -58,6 +58,13 @@ "email": "contact2@example.org" } ], + "creator": [ + { + "name": "Test Creator", + "email": "creator@example.org", + "url": "https://example.org" + } + ], "publisher": [ { "name": "Test Publisher", diff --git a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json index 08843e0d..2bf51257 100644 --- a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json +++ b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json @@ -53,6 +53,13 @@ "email": "contact2@example.org" } ], + "creator": [ + { + "name": "Test Creator", + "email": "creator@example.org", + "url": "https://example.org" + } + ], "publisher": [ { "name": "Test Publisher", @@ -64,7 +71,12 @@ { "start": "1905-03-01", "end": "2013-01-05" + }, + { + "start": "2024-04-10", + "end": "2024-05-29" } + ], "temporal_resolution": "PT15M", "spatial_coverage": [ From 82d1e0909e535761179ae1c5a3186483b53c230a Mon Sep 17 00:00:00 2001 From: amercader Date: Mon, 7 Oct 2024 13:42:43 +0200 Subject: [PATCH 07/26] Add missing creator to dcat-us schema --- ckanext/dcat/schemas/dcat_us_full.yaml | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/ckanext/dcat/schemas/dcat_us_full.yaml b/ckanext/dcat/schemas/dcat_us_full.yaml index 4585bd8c..8d81835f 100644 --- a/ckanext/dcat/schemas/dcat_us_full.yaml +++ b/ckanext/dcat/schemas/dcat_us_full.yaml @@ -72,6 +72,38 @@ dataset_fields: help_text: Unique identifier for the publisher, such as a ROR ID. help_text: Entity responsible for making the dataset available. +- field_name: creator + label: Creator + repeating_label: Creator + repeating_once: true + repeating_subfields: + + - field_name: uri + label: URI + help_text: URI of the creator, if available. + + - field_name: name + label: Name + help_text: Name of the entity or person who created the dataset. + + - field_name: email + label: Email + display_snippet: email.html + help_text: Contact email of the creator. + + - field_name: url + label: URL + display_snippet: link.html + help_text: URL for more information about the creator. + + - field_name: type + label: Type + help_text: Type of creator (e.g., Organization, Person). + + - field_name: identifier + label: Identifier + help_text: Unique identifier for the creator, such as an ORCID or ROR ID. + - field_name: license_id label: License form_snippet: license.html From 60009fb7ec8371f58583d1eb1ac6b3ae4f55f261 Mon Sep 17 00:00:00 2001 From: amercader Date: Mon, 7 Oct 2024 14:06:30 +0200 Subject: [PATCH 08/26] Extend DCAT US profile from DCAT AP 3 --- ckanext/dcat/profiles/dcat_us_3.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/ckanext/dcat/profiles/dcat_us_3.py b/ckanext/dcat/profiles/dcat_us_3.py index 60466761..9a2dd0be 100644 --- a/ckanext/dcat/profiles/dcat_us_3.py +++ b/ckanext/dcat/profiles/dcat_us_3.py @@ -1,18 +1,13 @@ -from rdflib import Literal, BNode - from ckanext.dcat.profiles import ( - DCAT, - XSD, - SKOS, - ADMS, - RDF, + DCT, ) +from ckanext.dcat.utils import resource_uri -from .euro_dcat_ap_2 import EuropeanDCATAP2Profile -from .euro_dcat_ap_scheming import EuropeanDCATAPSchemingProfile +from .base import URIRefOrLiteral, CleanedURIRef +from .euro_dcat_ap_3 import EuropeanDCATAP3Profile -class DCATUS3Profile(EuropeanDCATAP2Profile, EuropeanDCATAPSchemingProfile): +class DCATUS3Profile(EuropeanDCATAP3Profile): """ An RDF profile based on the DCAT-US 3 for data portals in the US """ @@ -28,6 +23,9 @@ def parse_dataset(self, dataset_dict, dataset_ref): # DCAT AP v2 scheming fields dataset_dict = self._parse_dataset_v2_scheming(dataset_dict, dataset_ref) + # DCAT US v3 properties also applied to higher versions + self._parse_dataset_v3_us(dataset_dict, dataset_ref) + return dataset_dict def graph_from_dataset(self, dataset_dict, dataset_ref): @@ -44,6 +42,9 @@ def graph_from_dataset(self, dataset_dict, dataset_ref): # DCAT AP v3 properties also applied to higher versions self._graph_from_dataset_v3(dataset_dict, dataset_ref) + # DCAT US v3 properties also applied to higher versions + self._graph_from_dataset_v3_us(dataset_dict, dataset_ref) + def graph_from_catalog(self, catalog_dict, catalog_ref): self._graph_from_catalog_base(catalog_dict, catalog_ref) From 8239e1bca7dea0b78d3438c78f16df32945b905a Mon Sep 17 00:00:00 2001 From: amercader Date: Mon, 7 Oct 2024 14:07:15 +0200 Subject: [PATCH 09/26] Add identifier property to Distributions --- ckanext/dcat/profiles/dcat_us_3.py | 50 +++++++------ ckanext/dcat/schemas/dcat_us_full.yaml | 6 +- .../dcat_us_3/test_dcat_us_3_profile_parse.py | 1 + .../test_dcat_us_3_profile_serialize.py | 74 ++++++++++++++++--- examples/dcat/dataset.rdf | 1 + 5 files changed, 96 insertions(+), 36 deletions(-) diff --git a/ckanext/dcat/profiles/dcat_us_3.py b/ckanext/dcat/profiles/dcat_us_3.py index 9a2dd0be..3ea5be1f 100644 --- a/ckanext/dcat/profiles/dcat_us_3.py +++ b/ckanext/dcat/profiles/dcat_us_3.py @@ -49,27 +49,29 @@ def graph_from_catalog(self, catalog_dict, catalog_ref): self._graph_from_catalog_base(catalog_dict, catalog_ref) - def _graph_from_dataset_v3(self, dataset_dict, dataset_ref): - - # byteSize decimal -> nonNegativeInteger - for subject, predicate, object in self.g.triples((None, DCAT.byteSize, None)): - if object and object.datatype == XSD.decimal: - self.g.remove((subject, predicate, object)) - - self.g.add( - ( - subject, - predicate, - Literal(int(object), datatype=XSD.nonNegativeInteger), - ) - ) - - # Other identifiers - value = self._get_dict_value(dataset_dict, "alternate_identifier") - if value: - items = self._read_list_value(value) - for item in items: - identifier = BNode() - self.g.add((dataset_ref, ADMS.identifier, identifier)) - self.g.add((identifier, RDF.type, ADMS.Identifier)) - self.g.add((identifier, SKOS.notation, Literal(item))) + def _parse_dataset_v3_us(self, dataset_dict, dataset_ref): + + for distribution_ref in self._distributions(dataset_ref): + + # Distribution identifier + value = self._object_value(distribution_ref, DCT.identifier) + if value: + for resource_dict in dataset_dict.get("resources", []): + if resource_dict["distribution_ref"] == str(distribution_ref): + resource_dict["identifier"] = value + + def _graph_from_dataset_v3_us(self, dataset_dict, dataset_ref): + + for resource_dict in dataset_dict.get("resources", []): + + distribution_ref = CleanedURIRef(resource_uri(resource_dict)) + + # Distribution identifier + self._add_triple_from_dict( + resource_dict, + distribution_ref, + DCT.identifier, + "identifier", + fallbacks=["guid", "id"], + _type=URIRefOrLiteral, + ) diff --git a/ckanext/dcat/schemas/dcat_us_full.yaml b/ckanext/dcat/schemas/dcat_us_full.yaml index 8d81835f..9d1f0206 100644 --- a/ckanext/dcat/schemas/dcat_us_full.yaml +++ b/ckanext/dcat/schemas/dcat_us_full.yaml @@ -147,7 +147,7 @@ dataset_fields: # Note: CKAN will generate a unique identifier for each dataset - field_name: identifier label: Identifier - help_text: A unique identifier of the dataset. + help_text: A unique identifier of the dataset, if not provided it will fall back to CKAN's internal id. - field_name: frequency label: Frequency @@ -354,6 +354,10 @@ resource_fields: display_snippet: link.html help_text: URL that provides a direct link to a downloadable file (defaults to the standard resource URL). +- field_name: identifier + label: Identifier + help_text: A unique identifier of the dataset, if not provided it will fall back to CKAN's internal id. + - field_name: issued label: Release date preset: dcat_date diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py index da5f4842..24414c7a 100644 --- a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py @@ -112,6 +112,7 @@ def test_e2e_dcat_to_ckan(self): # Resources: standard fields assert resource["license"] == "http://creativecommons.org/licenses/by-nc/2.0/" + assert resource["identifier"] == "https://example.org/distributions/1" assert resource["rights"] == "Some statement about rights" assert resource["issued"] == "2012-05-11" assert resource["modified"] == "2012-05-01T00:04:06" diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py index 2b1a3363..2284c558 100644 --- a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py @@ -29,17 +29,17 @@ DCAT_AP_PROFILES = ["dcat_us_3"] +@pytest.mark.usefixtures("with_plugins", "clean_db") +@pytest.mark.ckan_config("ckan.plugins", "dcat scheming_datasets") +@pytest.mark.ckan_config( + "scheming.dataset_schemas", "ckanext.dcat.schemas:dcat_us_full.yaml" +) +@pytest.mark.ckan_config( + "scheming.presets", + "ckanext.scheming:presets.json ckanext.dcat.schemas:presets.yaml", +) +@pytest.mark.ckan_config("ckanext.dcat.rdf.profiles", "dcat_us_3") class TestDCATUS3ProfileSerializeDataset(BaseSerializeTest): - @pytest.mark.usefixtures("with_plugins", "clean_db") - @pytest.mark.ckan_config("ckan.plugins", "dcat scheming_datasets") - @pytest.mark.ckan_config( - "scheming.dataset_schemas", "ckanext.dcat.schemas:dcat_us_full.yaml" - ) - @pytest.mark.ckan_config( - "scheming.presets", - "ckanext.scheming:presets.json ckanext.dcat.schemas:presets.yaml", - ) - @pytest.mark.ckan_config("ckanext.dcat.rdf.profiles", "dcat_us_3") def test_e2e_ckan_to_dcat(self): """ Create a dataset using the scheming schema, check that fields @@ -82,7 +82,9 @@ def test_e2e_ckan_to_dcat(self): ) assert self._triple(g, dataset_ref, DCT.type, URIRef(dataset["dcat_type"])) assert self._triple(g, dataset_ref, ADMS.versionNotes, dataset["version_notes"]) - assert self._triple(g, dataset_ref, DCT.accessRights, URIRef(dataset["access_rights"])) + assert self._triple( + g, dataset_ref, DCT.accessRights, URIRef(dataset["access_rights"]) + ) assert self._triple( g, dataset_ref, @@ -332,3 +334,53 @@ def test_e2e_ckan_to_dcat(self): ] assert endpoint_urls == resource["access_services"][0]["endpoint_url"] + def test_distribution_identifier(self): + + dataset_dict = { + "name": "test-dcat-us", + "description": "Test", + "resources": [ + { + "id": "89b67e5b-d0e1-4bc3-a75a-59f21c66ebc0", + "name": "some data", + "identifier": "https://example.org/distributions/1", + } + ], + } + + s = RDFSerializer(profiles=DCAT_AP_PROFILES) + g = s.g + + dataset_ref = s.graph_from_dataset(dataset_dict) + + distribution_ref = self._triple(g, dataset_ref, DCAT.distribution, None)[2] + resource = dataset_dict["resources"][0] + + assert self._triple( + g, distribution_ref, DCT.identifier, URIRef(resource["identifier"]) + ) + + def test_distribution_identifier_falls_back_to_id(self): + + dataset_dict = { + "name": "test-dcat-us", + "description": "Test", + "resources": [ + { + "id": "89b67e5b-d0e1-4bc3-a75a-59f21c66ebc0", + "name": "some data", + } + ], + } + + s = RDFSerializer(profiles=DCAT_AP_PROFILES) + g = s.g + + dataset_ref = s.graph_from_dataset(dataset_dict) + + distribution_ref = self._triple(g, dataset_ref, DCAT.distribution, None)[2] + resource = dataset_dict["resources"][0] + + assert self._triple( + g, distribution_ref, DCT.identifier, resource["id"] + ) diff --git a/examples/dcat/dataset.rdf b/examples/dcat/dataset.rdf index 8cd9619f..26212d4b 100644 --- a/examples/dcat/dataset.rdf +++ b/examples/dcat/dataset.rdf @@ -91,6 +91,7 @@ Some website A longer description + https://example.org/distributions/1 2012-05-11 2012-05-01T00:04:06 From 90224d157e71e719ba18143f157963bbe39454d2 Mon Sep 17 00:00:00 2001 From: amercader Date: Mon, 7 Oct 2024 15:27:28 +0200 Subject: [PATCH 10/26] Tweaks on publisher class for dcat-us Use org:Organization and add a skos:prefLabel property --- ckanext/dcat/profiles/base.py | 3 ++- ckanext/dcat/profiles/dcat_us_3.py | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/ckanext/dcat/profiles/base.py b/ckanext/dcat/profiles/base.py index 9169abbf..15e26ecb 100644 --- a/ckanext/dcat/profiles/base.py +++ b/ckanext/dcat/profiles/base.py @@ -4,7 +4,7 @@ from dateutil.parser import parse as parse_date from rdflib import term, URIRef, BNode, Literal -from rdflib.namespace import Namespace, RDF, XSD, SKOS, RDFS +from rdflib.namespace import Namespace, RDF, XSD, SKOS, RDFS, ORG from geomet import wkt, InvalidGeoJSONException from ckantoolkit import config, url_for, asbool, aslist, get_action, ObjectNotFound @@ -39,6 +39,7 @@ "locn": LOCN, "gsp": GSP, "owl": OWL, + "org": ORG, "spdx": SPDX, } diff --git a/ckanext/dcat/profiles/dcat_us_3.py b/ckanext/dcat/profiles/dcat_us_3.py index 3ea5be1f..33f77934 100644 --- a/ckanext/dcat/profiles/dcat_us_3.py +++ b/ckanext/dcat/profiles/dcat_us_3.py @@ -1,9 +1,14 @@ +from rdflib import Literal + from ckanext.dcat.profiles import ( DCT, + FOAF, + RDF, + SKOS, ) from ckanext.dcat.utils import resource_uri -from .base import URIRefOrLiteral, CleanedURIRef +from .base import URIRefOrLiteral, CleanedURIRef, ORG from .euro_dcat_ap_3 import EuropeanDCATAP3Profile @@ -62,6 +67,19 @@ def _parse_dataset_v3_us(self, dataset_dict, dataset_ref): def _graph_from_dataset_v3_us(self, dataset_dict, dataset_ref): + g = self.g + + for publisher_ref in g.objects(dataset_ref, DCT.publisher): + + # Use org:Organization instead of foaf:Agent + g.remove((publisher_ref, RDF.type, None)) + g.add((publisher_ref, RDF.type, ORG.Organization)) + + # Add skos:prefLabel + name = self._object_value(publisher_ref, FOAF.name) + if name: + g.add((publisher_ref, SKOS.prefLabel, Literal(name))) + for resource_dict in dataset_dict.get("resources", []): distribution_ref = CleanedURIRef(resource_uri(resource_dict)) From 4dd41f65b09428432fa1641879df0bb1a3d4d2a8 Mon Sep 17 00:00:00 2001 From: amercader Date: Tue, 8 Oct 2024 14:22:12 +0200 Subject: [PATCH 11/26] Remove foaf:Document class From landing page and documentation if there is no title defined for them. See Usage note in https://doi-do.github.io/dcat-us/#properties-for-document --- ckanext/dcat/profiles/dcat_us_3.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ckanext/dcat/profiles/dcat_us_3.py b/ckanext/dcat/profiles/dcat_us_3.py index 33f77934..efaf88fe 100644 --- a/ckanext/dcat/profiles/dcat_us_3.py +++ b/ckanext/dcat/profiles/dcat_us_3.py @@ -1,6 +1,7 @@ from rdflib import Literal from ckanext.dcat.profiles import ( + DCAT, DCT, FOAF, RDF, @@ -69,6 +70,16 @@ def _graph_from_dataset_v3_us(self, dataset_dict, dataset_ref): g = self.g + # Remove foaf:Document class from landing page and documentation if there + # is no title defined for them + # See Usage note in https://doi-do.github.io/dcat-us/#properties-for-document + for page_ref in g.objects(dataset_ref, DCAT.landingPage): + if not len([t for t in g.triples((page_ref, DCT.title, None))]): + g.remove((page_ref, RDF.type, None)) + for doc_ref in g.objects(dataset_ref, FOAF.page): + if not len([t for t in g.triples((page_ref, DCT.title, None))]): + g.remove((doc_ref, RDF.type, None)) + for publisher_ref in g.objects(dataset_ref, DCT.publisher): # Use org:Organization instead of foaf:Agent From 7eff792818aeced88ce477f5207cefe0a268502e Mon Sep 17 00:00:00 2001 From: amercader Date: Tue, 8 Oct 2024 14:23:00 +0200 Subject: [PATCH 12/26] Don't test access_services for now As they are modeled as Data Services, the outputs fails validation because they don't contain publisher and contact point. We can revisit this if we implement first class support for Data Services --- .../ckan_full_dataset_dcat_us_vocabularies.json | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json index 2bf51257..6de96f81 100644 --- a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json +++ b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json @@ -173,20 +173,6 @@ "http://id.loc.gov/vocabulary/iso639-1/en", "http://id.loc.gov/vocabulary/iso639-1/es", "http://id.loc.gov/vocabulary/iso639-1/ca" - ], - "access_services": [ - { - "title": "Access Service 1", - "endpoint_description": "https://example.org/endpoint_description", - "endpoint_url": [ - "https://example.org/access_service/1", - "https://example.org/access_service/2" - ], - "serves_dataset": [ - "https://example.org/dataset/1", - "https://example.org/dataset/2" - ] - } ] } ] From 8fd12846f81eadd7542964ac809473c3fa6689b9 Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 9 Oct 2024 12:18:02 +0200 Subject: [PATCH 13/26] Improve serialization of statements Rather than just include a Literal, add it as a node (with a class if provided) with a RDFS.label property, eg: dct:accessRights [ a dct:RightsStatement ; rdfs:label "Statement about access rights" ] ; An URI can also be used: dct:accessRights ; [...] a dct:RightsStatement . This is in line with the recommendation in the DCAT 3 spec: https://www.w3.org/TR/vocab-dcat-3/#ex-license-and-access-rights --- ckanext/dcat/profiles/base.py | 32 +++++++++++++++++++ ckanext/dcat/profiles/euro_dcat_ap_base.py | 29 +++++++++++++++-- .../test_euro_dcatap_profile_serialize.py | 16 ++++++++-- .../dcat_ap_2/test_scheming_support.py | 16 ++++++++-- .../test_euro_dcatap_3_profile_serialize.py | 16 ++++++++-- 5 files changed, 97 insertions(+), 12 deletions(-) diff --git a/ckanext/dcat/profiles/base.py b/ckanext/dcat/profiles/base.py index 15e26ecb..51a582c1 100644 --- a/ckanext/dcat/profiles/base.py +++ b/ckanext/dcat/profiles/base.py @@ -757,6 +757,38 @@ def _add_spatial_to_dict(self, dataset_dict, key, spatial): } ) + def _add_statement_to_graph(self, data_dict, key, subject, predicate, _class=None): + """ + Adds a statement property to the graph. + If it is a Literal value, it is added as a node (with a class if provided) + with a RDFS.label property, eg: + + dct:accessRights [ a dct:RightsStatement ; + rdfs:label "Statement about access rights" ] ; + + An URI can also be used: + + dct:accessRights ; + + [...] + + a dct:RightsStatement . + """ + value = self._get_dict_value(data_dict, key) + if value: + _object = URIRefOrLiteral(value) + if isinstance(_object, Literal): + statement_ref = BNode() + self.g.add((subject, predicate, statement_ref)) + if _class: + self.g.add((statement_ref, RDF.type, _class)) + self.g.add((statement_ref, RDFS.label, _object)) + + else: + self.g.add((subject, predicate, _object)) + if _class: + self.g.add((_object, RDF.type, _class)) + def _schema_field(self, key): """ Returns the schema field information if the provided key exists as a field in diff --git a/ckanext/dcat/profiles/euro_dcat_ap_base.py b/ckanext/dcat/profiles/euro_dcat_ap_base.py index f1db48b6..12aedab5 100644 --- a/ckanext/dcat/profiles/euro_dcat_ap_base.py +++ b/ckanext/dcat/profiles/euro_dcat_ap_base.py @@ -282,9 +282,7 @@ def _graph_from_dataset_base(self, dataset_dict, dataset_ref): ("version", OWL.versionInfo, ["dcat_version"], Literal), ("version_notes", ADMS.versionNotes, None, Literal), ("frequency", DCT.accrualPeriodicity, None, URIRefOrLiteral, DCT.Frequency), - ("access_rights", DCT.accessRights, None, URIRefOrLiteral, DCT.AccessRights), ("dcat_type", DCT.type, None, URIRefOrLiteral), - ("provenance", DCT.provenance, None, URIRefOrLiteral, DCT.ProvenanceStatement), ] self._add_triples_from_dict(dataset_dict, dataset_ref, items) @@ -500,6 +498,23 @@ def _graph_from_dataset_base(self, dataset_dict, dataset_ref): ): resource_license_fallback = dataset_dict["license_url"] + # Statetements + self._add_statement_to_graph( + dataset_dict, + "access_rights", + dataset_ref, + DCT.accessRights, + DCT.RightsStatement + ) + + self._add_statement_to_graph( + dataset_dict, + "provenance", + dataset_ref, + DCT.provenance, + DCT.ProvenanceStatement + ) + # Resources for resource_dict in dataset_dict.get("resources", []): @@ -514,7 +529,6 @@ def _graph_from_dataset_base(self, dataset_dict, dataset_ref): ("name", DCT.title, None, Literal), ("description", DCT.description, None, Literal), ("status", ADMS.status, None, URIRefOrLiteral), - ("rights", DCT.rights, None, URIRefOrLiteral, DCT.RightsStatement), ("license", DCT.license, None, URIRefOrLiteral, DCT.LicenseDocument), ("access_url", DCAT.accessURL, None, URIRef, RDFS.Resource), ("download_url", DCAT.downloadURL, None, URIRef, RDFS.Resource), @@ -530,6 +544,15 @@ def _graph_from_dataset_base(self, dataset_dict, dataset_ref): ] self._add_list_triples_from_dict(resource_dict, distribution, items) + # Statetements + self._add_statement_to_graph( + resource_dict, + "rights", + distribution, + DCT.rights, + DCT.RightsStatement + ) + # Set default license for distribution if needed and available if resource_license_fallback and not (distribution, DCT.license, None) in g: diff --git a/ckanext/dcat/tests/profiles/dcat_ap/test_euro_dcatap_profile_serialize.py b/ckanext/dcat/tests/profiles/dcat_ap/test_euro_dcatap_profile_serialize.py index ddbf9e21..9ba200be 100644 --- a/ckanext/dcat/tests/profiles/dcat_ap/test_euro_dcatap_profile_serialize.py +++ b/ckanext/dcat/tests/profiles/dcat_ap/test_euro_dcatap_profile_serialize.py @@ -21,6 +21,7 @@ from ckanext.dcat.profiles import ( DCAT, DCT, ADMS, XSD, VCARD, FOAF, SCHEMA, SKOS, LOCN, GSP, OWL, SPDX, GEOJSON_IMT, + RDFS, ) from ckanext.dcat.profiles.euro_dcat_ap_base import DISTRIBUTION_LICENSE_FALLBACK_CONFIG from ckanext.dcat.utils import DCAT_EXPOSE_SUBCATALOGS @@ -121,8 +122,6 @@ def test_graph_from_dataset(self): assert self._triple(g, dataset_ref, OWL.versionInfo, dataset['version']) assert self._triple(g, dataset_ref, ADMS.versionNotes, extras['version_notes']) assert self._triple(g, dataset_ref, DCT.accrualPeriodicity, extras['frequency']) - assert self._triple(g, dataset_ref, DCT.accessRights, extras['access_rights']) - assert self._triple(g, dataset_ref, DCT.provenance, extras['provenance']) assert self._triple(g, dataset_ref, DCT.type, extras['dcat_type']) # Tags @@ -156,6 +155,14 @@ def test_graph_from_dataset(self): _type = item[2][num] assert self._triple(g, dataset_ref, item[1], _type(value)) + # Statements + for item in [ + ('access_rights', DCT.accessRights), + ('provenance', DCT.provenance), + ]: + statement = [s for s in g.objects(dataset_ref, item[1])][0] + assert self._triple(g, statement, RDFS.label, extras[item[0]]) + def test_identifier_extra(self): dataset = { 'id': '4b6fe9ca-dc77-4cec-92a4-55c6624a5bd6', @@ -670,7 +677,6 @@ def test_distribution_fields(self): assert self._triple(g, distribution, RDF.type, DCAT.Distribution) assert self._triple(g, distribution, DCT.title, resource['name']) assert self._triple(g, distribution, DCT.description, resource['description']) - assert self._triple(g, distribution, DCT.rights, resource['rights']) assert self._triple(g, distribution, DCT.license, URIRef(resource['license'])) assert self._triple(g, distribution, ADMS.status, URIRef(resource['status'])) @@ -703,6 +709,10 @@ def test_distribution_fields(self): assert self._triple(g, checksum, SPDX.checksumValue, resource['hash'], data_type='http://www.w3.org/2001/XMLSchema#hexBinary') assert self._triple(g, checksum, SPDX.algorithm, URIRef(resource['hash_algorithm'])) + # Statements + statement = [s for s in g.objects(distribution, DCT.rights)][0] + assert self._triple(g, statement, RDFS.label, resource['rights']) + def test_distribution_size_not_number(self): resource = { diff --git a/ckanext/dcat/tests/profiles/dcat_ap_2/test_scheming_support.py b/ckanext/dcat/tests/profiles/dcat_ap_2/test_scheming_support.py index 65b2c074..507c762c 100644 --- a/ckanext/dcat/tests/profiles/dcat_ap_2/test_scheming_support.py +++ b/ckanext/dcat/tests/profiles/dcat_ap_2/test_scheming_support.py @@ -25,6 +25,7 @@ GSP, OWL, SPDX, + RDFS, ) from ckanext.dcat.tests.utils import BaseSerializeTest, BaseParseTest @@ -76,10 +77,8 @@ def test_e2e_ckan_to_dcat(self): assert self._triple( g, dataset_ref, DCT.accrualPeriodicity, dataset["frequency"] ) - assert self._triple(g, dataset_ref, DCT.provenance, dataset["provenance"]) assert self._triple(g, dataset_ref, DCT.type, dataset["dcat_type"]) assert self._triple(g, dataset_ref, ADMS.versionNotes, dataset["version_notes"]) - assert self._triple(g, dataset_ref, DCT.accessRights, dataset["access_rights"]) assert self._triple( g, dataset_ref, @@ -274,6 +273,14 @@ def test_e2e_ckan_to_dcat(self): distribution_ref = self._triple(g, dataset_ref, DCAT.distribution, None)[2] resource = dataset_dict["resources"][0] + # Statements + for item in [ + ('access_rights', DCT.accessRights), + ('provenance', DCT.provenance), + ]: + statement = [s for s in g.objects(dataset_ref, item[1])][0] + assert self._triple(g, statement, RDFS.label, dataset[item[0]]) + # Resources: core fields assert self._triple(g, distribution_ref, DCT.title, resource["name"]) @@ -286,7 +293,6 @@ def test_e2e_ckan_to_dcat(self): # Resources: standard fields - assert self._triple(g, distribution_ref, DCT.rights, resource["rights"]) assert self._triple( g, distribution_ref, ADMS.status, URIRef(resource["status"]) ) @@ -380,6 +386,10 @@ def test_e2e_ckan_to_dcat(self): ] assert endpoint_urls == resource["access_services"][0]["endpoint_url"] + # Resources: statements + statement = [s for s in g.objects(distribution_ref, DCT.rights)][0] + assert self._triple(g, statement, RDFS.label, resource['rights']) + def test_publisher_fallback_org(self): org = factories.Organization( diff --git a/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_serialize.py b/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_serialize.py index 893ce1d4..a17e2cc9 100644 --- a/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_serialize.py +++ b/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_serialize.py @@ -24,6 +24,7 @@ GSP, OWL, SPDX, + RDFS, ) DCAT_AP_PROFILES = ["euro_dcat_ap_3"] @@ -74,10 +75,8 @@ def test_e2e_ckan_to_dcat(self): assert self._triple( g, dataset_ref, DCT.accrualPeriodicity, dataset["frequency"] ) - assert self._triple(g, dataset_ref, DCT.provenance, dataset["provenance"]) assert self._triple(g, dataset_ref, DCT.type, dataset["dcat_type"]) assert self._triple(g, dataset_ref, ADMS.versionNotes, dataset["version_notes"]) - assert self._triple(g, dataset_ref, DCT.accessRights, dataset["access_rights"]) assert self._triple( g, dataset_ref, @@ -230,6 +229,14 @@ def test_e2e_ckan_to_dcat(self): distribution_ref = self._triple(g, dataset_ref, DCAT.distribution, None)[2] resource = dataset_dict["resources"][0] + # Statements + for item in [ + ('access_rights', DCT.accessRights), + ('provenance', DCT.provenance), + ]: + statement = [s for s in g.objects(dataset_ref, item[1])][0] + assert self._triple(g, statement, RDFS.label, dataset[item[0]]) + # Alternate identifiers ids = [] for subject in [t[2] for t in g.triples((dataset_ref, ADMS.identifier, None))]: @@ -248,7 +255,6 @@ def test_e2e_ckan_to_dcat(self): # Resources: standard fields - assert self._triple(g, distribution_ref, DCT.rights, resource["rights"]) assert self._triple( g, distribution_ref, ADMS.status, URIRef(resource["status"]) ) @@ -343,6 +349,10 @@ def test_e2e_ckan_to_dcat(self): ] assert endpoint_urls == resource["access_services"][0]["endpoint_url"] + # Resources: statements + statement = [s for s in g.objects(distribution_ref, DCT.rights)][0] + assert self._triple(g, statement, RDFS.label, resource['rights']) + def test_byte_size_non_negative_integer(self): dataset = { From 9569479d84c0556eaa207435e6c3476bd4b9e2fe Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 9 Oct 2024 14:49:01 +0200 Subject: [PATCH 14/26] Update dcat us 3 test --- .../test_dcat_us_3_profile_serialize.py | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py index 2284c558..7af56b2e 100644 --- a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py @@ -24,6 +24,7 @@ GSP, OWL, SPDX, + RDFS, ) DCAT_AP_PROFILES = ["dcat_us_3"] @@ -245,7 +246,6 @@ def test_e2e_ckan_to_dcat(self): # Resources: standard fields - assert self._triple(g, distribution_ref, DCT.rights, resource["rights"]) assert self._triple( g, distribution_ref, ADMS.status, URIRef(resource["status"]) ) @@ -315,24 +315,9 @@ def test_e2e_ckan_to_dcat(self): == resource["language"] ) - # Resource: repeating subfields - access_services = [ - t for t in g.triples((distribution_ref, DCAT.accessService, None)) - ] - - assert len(access_services) == len(dataset["resources"][0]["access_services"]) - assert self._triple( - g, - access_services[0][2], - DCT.title, - resource["access_services"][0]["title"], - ) - - endpoint_urls = [ - str(t[2]) - for t in g.triples((access_services[0][2], DCAT.endpointURL, None)) - ] - assert endpoint_urls == resource["access_services"][0]["endpoint_url"] + # Resources: statements + statement = [s for s in g.objects(distribution_ref, DCT.rights)][0] + assert self._triple(g, statement, RDFS.label, resource['rights']) def test_distribution_identifier(self): From 404007405d199aa4f8fc95f112503d490570abca Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 9 Oct 2024 14:49:38 +0200 Subject: [PATCH 15/26] Index strings for repeating subfields Values like floats caused an exception here, and the fields get index as text in Solr anyway --- ckanext/dcat/plugins/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ckanext/dcat/plugins/__init__.py b/ckanext/dcat/plugins/__init__.py index e584de34..1477a38f 100644 --- a/ckanext/dcat/plugins/__init__.py +++ b/ckanext/dcat/plugins/__init__.py @@ -183,9 +183,9 @@ def before_dataset_index(self, dataset_dict): # Index a flattened version new_key = f'extras_{field["field_name"]}__{key}' if not dataset_dict.get(new_key): - dataset_dict[new_key] = value + dataset_dict[new_key] = str(value) else: - dataset_dict[new_key] += ' ' + value + dataset_dict[new_key] += ' ' + str(value) subfields = dataset_dict.pop(field['field_name'], None) if field['field_name'] == 'spatial_coverage': From 712b8049a3e3a3ddc3ce7d82a92cd16f1d49d95e Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 9 Oct 2024 15:30:39 +0200 Subject: [PATCH 16/26] Add bounding box property --- ckanext/dcat/profiles/__init__.py | 1 + ckanext/dcat/profiles/base.py | 2 + ckanext/dcat/profiles/dcat_us_3.py | 50 ++++++++++++++++++- ckanext/dcat/schemas/dcat_us_full.yaml | 17 +++++++ .../dcat_us_3/test_dcat_us_3_profile_parse.py | 36 +++++++++++++ .../test_dcat_us_3_profile_serialize.py | 49 ++++++++++++++++-- ckanext/dcat/tests/shacl/test_shacl.py | 1 + ...kan_full_dataset_dcat_us_vocabularies.json | 15 +++++- 8 files changed, 165 insertions(+), 6 deletions(-) diff --git a/ckanext/dcat/profiles/__init__.py b/ckanext/dcat/profiles/__init__.py index 958faa9c..6eff474e 100644 --- a/ckanext/dcat/profiles/__init__.py +++ b/ckanext/dcat/profiles/__init__.py @@ -6,6 +6,7 @@ RDFS, DCAT, DCATAP, + DCATUS, DCT, ADMS, VCARD, diff --git a/ckanext/dcat/profiles/base.py b/ckanext/dcat/profiles/base.py index 51a582c1..3d031704 100644 --- a/ckanext/dcat/profiles/base.py +++ b/ckanext/dcat/profiles/base.py @@ -16,6 +16,7 @@ DCT = Namespace("http://purl.org/dc/terms/") DCAT = Namespace("http://www.w3.org/ns/dcat#") DCATAP = Namespace("http://data.europa.eu/r5r/") +DCATUS = Namespace("http://resources.data.gov/ontology/dcat-us#") ADMS = Namespace("http://www.w3.org/ns/adms#") VCARD = Namespace("http://www.w3.org/2006/vcard/ns#") FOAF = Namespace("http://xmlns.com/foaf/0.1/") @@ -30,6 +31,7 @@ "dct": DCT, "dcat": DCAT, "dcatap": DCATAP, + "dcatus": DCATUS, "adms": ADMS, "vcard": VCARD, "foaf": FOAF, diff --git a/ckanext/dcat/profiles/dcat_us_3.py b/ckanext/dcat/profiles/dcat_us_3.py index efaf88fe..f7cf3cb6 100644 --- a/ckanext/dcat/profiles/dcat_us_3.py +++ b/ckanext/dcat/profiles/dcat_us_3.py @@ -1,11 +1,15 @@ -from rdflib import Literal +from decimal import Decimal, DecimalException + +from rdflib import Literal, BNode from ckanext.dcat.profiles import ( DCAT, + DCATUS, DCT, FOAF, RDF, SKOS, + XSD, ) from ckanext.dcat.utils import resource_uri @@ -57,6 +61,21 @@ def graph_from_catalog(self, catalog_dict, catalog_ref): def _parse_dataset_v3_us(self, dataset_dict, dataset_ref): + g = self.g + + # Bounding box + for bbox_ref in g.objects(dataset_ref, DCATUS.geographicBoundingBox): + if not dataset_dict.get("bbox"): + dataset_dict["bbox"] = [] + dataset_dict["bbox"].append( + { + "west": self._object_value(bbox_ref, DCATUS.westBoundingLongitude), + "east": self._object_value(bbox_ref, DCATUS.eastBoundingLongitude), + "north": self._object_value(bbox_ref, DCATUS.northBoundingLatitude), + "south": self._object_value(bbox_ref, DCATUS.southBoundingLatitude), + } + ) + for distribution_ref in self._distributions(dataset_ref): # Distribution identifier @@ -91,6 +110,35 @@ def _graph_from_dataset_v3_us(self, dataset_dict, dataset_ref): if name: g.add((publisher_ref, SKOS.prefLabel, Literal(name))) + # Bounding box + # TODO: we could fall back to spatial or spatial_coverage's bbox/geom + bboxes = self._get_dataset_value(dataset_dict, "bbox") + if bboxes: + for bbox in bboxes: + bbox_ref = BNode() + g.add((dataset_ref, DCATUS.geographicBoundingBox, bbox_ref)) + g.add((bbox_ref, RDF.type, DCATUS.geographicBoundingBox)) + + def add_bounding(predicate, value): + try: + g.add( + ( + bbox_ref, + predicate, + Literal(value, datatype=XSD.decimal), + ) + ) + except (ValueError, TypeError, DecimalException): + g.add((bbox_ref, predicate, Literal(value))) + + for item in ( + (DCATUS.westBoundingLongitude, bbox["west"]), + (DCATUS.eastBoundingLongitude, bbox["east"]), + (DCATUS.northBoundingLatitude, bbox["north"]), + (DCATUS.southBoundingLatitude, bbox["south"]), + ): + add_bounding(item[0], item[1]) + for resource_dict in dataset_dict.get("resources", []): distribution_ref = CleanedURIRef(resource_uri(resource_dict)) diff --git a/ckanext/dcat/schemas/dcat_us_full.yaml b/ckanext/dcat/schemas/dcat_us_full.yaml index 9d1f0206..6199f654 100644 --- a/ckanext/dcat/schemas/dcat_us_full.yaml +++ b/ckanext/dcat/schemas/dcat_us_full.yaml @@ -181,6 +181,23 @@ dataset_fields: label: Temporal resolution help_text: Minimum time period resolvable in the dataset. +- field_name: bbox + label: Geographic Bounding Box + repeating_subfields: + + - field_name: west + label: West Longitude + + - field_name: east + label: East Longitude + + - field_name: north + label: North Latitude + + - field_name: south + label: South Latitude + help_text: A geographic bounding box in WGS84 coordinate system (Lat/Long) that describes the spatial extent of the dataset. + - field_name: spatial_coverage label: Spatial coverage repeating_subfields: diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py index 24414c7a..9e67c719 100644 --- a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py @@ -151,3 +151,39 @@ def test_e2e_dcat_to_ckan(self): assert resource["access_services"][0]["endpoint_url"] == [ "http://publications.europa.eu/webapi/rdf/sparql" ] + + def test_bbox(self): + + data = """ + @prefix dcat: . + @prefix dcat-us: . + @prefix dcterms: . + @prefix locn: . + @prefix gsp: . + + + a dcat:Dataset ; + dcterms:title "Dataset 1" ; + dcterms:description "This is a dataset" ; + dcterms:publisher ; + dcat-us:geographicBoundingBox [ + a dcat-us:GeographicBoundingBox ; + dcat-us:westBoundingLongitude 22.3; + dcat-us:eastBoundingLongitude 10.3; + dcat-us:northBoundingLatitude 50.2; + dcat-us:southBoundingLatitude 20.2; + ] + . + """ + p = RDFParser() + + p.parse(data, _format="ttl") + + datasets = [d for d in p.datasets()] + + dataset = datasets[0] + + assert dataset["bbox"][0]["west"] == "22.3" + assert dataset["bbox"][0]["east"] == "10.3" + assert dataset["bbox"][0]["north"] == "50.2" + assert dataset["bbox"][0]["south"] == "20.2" diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py index 7af56b2e..7e0fadbd 100644 --- a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py @@ -11,9 +11,8 @@ from ckanext.dcat import utils from ckanext.dcat.profiles import ( - RDF, DCAT, - DCATAP, + DCATUS, DCT, ADMS, XSD, @@ -317,7 +316,7 @@ def test_e2e_ckan_to_dcat(self): # Resources: statements statement = [s for s in g.objects(distribution_ref, DCT.rights)][0] - assert self._triple(g, statement, RDFS.label, resource['rights']) + assert self._triple(g, statement, RDFS.label, resource["rights"]) def test_distribution_identifier(self): @@ -366,6 +365,48 @@ def test_distribution_identifier_falls_back_to_id(self): distribution_ref = self._triple(g, dataset_ref, DCAT.distribution, None)[2] resource = dataset_dict["resources"][0] + assert self._triple(g, distribution_ref, DCT.identifier, resource["id"]) + + def test_bbox(self): + dataset_dict = { + "name": "test-dcat-us", + "description": "Test", + "bbox": [ + {"west": -179.15, "east": -129.98, "north": 71.54, "south": 51.21} + ], + } + + s = RDFSerializer(profiles=DCAT_AP_PROFILES) + g = s.g + + dataset_ref = s.graph_from_dataset(dataset_dict) + + bbox_ref = [s for s in g.objects(dataset_ref, DCATUS.geographicBoundingBox)][0] + assert self._triple( + g, + bbox_ref, + DCATUS.westBoundingLongitude, + dataset_dict["bbox"][0]["west"], + data_type=XSD.decimal, + ) + assert self._triple( + g, + bbox_ref, + DCATUS.eastBoundingLongitude, + dataset_dict["bbox"][0]["east"], + data_type=XSD.decimal, + ) assert self._triple( - g, distribution_ref, DCT.identifier, resource["id"] + g, + bbox_ref, + DCATUS.northBoundingLatitude, + dataset_dict["bbox"][0]["north"], + data_type=XSD.decimal, + ) + assert self._triple( + g, + bbox_ref, + DCATUS.southBoundingLatitude, + dataset_dict["bbox"][0]["south"], + data_type=XSD.decimal, ) diff --git a/ckanext/dcat/tests/shacl/test_shacl.py b/ckanext/dcat/tests/shacl/test_shacl.py index dadf0071..cf75faef 100644 --- a/ckanext/dcat/tests/shacl/test_shacl.py +++ b/ckanext/dcat/tests/shacl/test_shacl.py @@ -215,6 +215,7 @@ def test_validate_dcat_us_3_graph(): graph = graph_from_dataset("ckan_full_dataset_dcat_us_vocabularies.json") + graph.serialize(destination="graph.ttl") path = _get_shacl_file_path("dcat-us_3.0_shacl_shapes.ttl") r = validate(graph, shacl_graph=path) conforms, results_graph, results_text = r diff --git a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json index 6de96f81..d507e73a 100644 --- a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json +++ b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json @@ -76,9 +76,22 @@ "start": "2024-04-10", "end": "2024-05-29" } - ], "temporal_resolution": "PT15M", + "bbox": [ + { + "west": -179.15, + "east": -129.98, + "north": 71.54, + "south": 51.21 + }, + { + "west": -125.0, + "east": -66.9, + "north": 49.4, + "south": 24.4 + } + ], "spatial_coverage": [ { "geom": { From 44a11fdadb793b5223686332cda07d728e328b7f Mon Sep 17 00:00:00 2001 From: amercader Date: Mon, 14 Oct 2024 15:26:12 +0200 Subject: [PATCH 17/26] Add temporal_resolution to distributions --- ckanext/dcat/profiles/dcat_us_3.py | 14 ++++++++++++++ ckanext/dcat/schemas/dcat_us_full.yaml | 4 ++++ .../dcat_us_3/test_dcat_us_3_profile_parse.py | 1 + .../dcat_us_3/test_dcat_us_3_profile_serialize.py | 8 ++++++++ .../ckan_full_dataset_dcat_us_vocabularies.json | 1 + examples/dcat/dataset.rdf | 1 + 6 files changed, 29 insertions(+) diff --git a/ckanext/dcat/profiles/dcat_us_3.py b/ckanext/dcat/profiles/dcat_us_3.py index f7cf3cb6..491b4328 100644 --- a/ckanext/dcat/profiles/dcat_us_3.py +++ b/ckanext/dcat/profiles/dcat_us_3.py @@ -85,6 +85,11 @@ def _parse_dataset_v3_us(self, dataset_dict, dataset_ref): if resource_dict["distribution_ref"] == str(distribution_ref): resource_dict["identifier"] = value + # Temporal resolution + value = self._object_value(distribution_ref, DCAT.temporalResolution) + if value: + resource_dict["temporal_resolution"] = value + def _graph_from_dataset_v3_us(self, dataset_dict, dataset_ref): g = self.g @@ -152,3 +157,12 @@ def add_bounding(predicate, value): fallbacks=["guid", "id"], _type=URIRefOrLiteral, ) + + # Temporal resolution + self._add_triple_from_dict( + resource_dict, + distribution_ref, + DCAT.temporalResolution, + "temporal_resolution", + _datatype=XSD.duration, + ) diff --git a/ckanext/dcat/schemas/dcat_us_full.yaml b/ckanext/dcat/schemas/dcat_us_full.yaml index 6199f654..346c7545 100644 --- a/ckanext/dcat/schemas/dcat_us_full.yaml +++ b/ckanext/dcat/schemas/dcat_us_full.yaml @@ -385,6 +385,10 @@ resource_fields: preset: dcat_date help_text: Most recent date on which the resource was changed, updated or modified. +- field_name: temporal_resolution + label: Temporal resolution + help_text: Minimum time period resolvable in the distribution. + - field_name: language label: Language preset: multiple_text diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py index 9e67c719..9a0c75a9 100644 --- a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py @@ -118,6 +118,7 @@ def test_e2e_dcat_to_ckan(self): assert resource["modified"] == "2012-05-01T00:04:06" assert resource["status"] == "http://purl.org/adms/status/Completed" assert resource["size"] == 12323 + assert resource["temporal_resolution"] == "PT15M" assert ( resource["compress_format"] == "http://www.iana.org/assignments/media-types/application/gzip" diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py index 7e0fadbd..4ab6ef6e 100644 --- a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py @@ -277,6 +277,14 @@ def test_e2e_ckan_to_dcat(self): g, distribution_ref, DCAT.byteSize, resource["size"], XSD.nonNegativeInteger ) + assert self._triple( + g, + distribution_ref, + DCAT.temporalResolution, + resource["temporal_resolution"], + data_type=XSD.duration, + ) + # Checksum checksum = self._triple(g, distribution_ref, SPDX.checksum, None)[2] assert checksum diff --git a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json index d507e73a..a01f1fa9 100644 --- a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json +++ b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json @@ -177,6 +177,7 @@ "download_url": "https://example.com/data.csv", "issued": "2024-05-01T01:20:33", "modified": "2024-05-05T09:33:20", + "temporal_resolution": "PT15M", "license": "https://resources.data.gov/vocab/license/TODO/CC_BYNC_4_0", "rights": "This dataset is provided by the United States Malacological Commission (USMC)", "conforms_to": [ diff --git a/examples/dcat/dataset.rdf b/examples/dcat/dataset.rdf index 26212d4b..273f4724 100644 --- a/examples/dcat/dataset.rdf +++ b/examples/dcat/dataset.rdf @@ -101,6 +101,7 @@ http://www.bgs.ac.uk/gbase/geochemcd/home.html HTML text/html + PT15M http://www.iana.org/assignments/media-types/application/gzip http://publications.europa.eu/resource/authority/file-type/TAR 12323 From bd346a0c25fc269376ca3239fe2b91247e919010 Mon Sep 17 00:00:00 2001 From: amercader Date: Thu, 17 Oct 2024 11:58:26 +0200 Subject: [PATCH 18/26] Add support for data dictionary properties Both at the dataset and distribution level. This are implemented as (single) repeating subfields with url, format and license properties supported. Potential future improvements: license fallback to dataset's and link to datastore data dictionary automatically --- ckanext/dcat/profiles/dcat_us_3.py | 114 ++++++++++++-- ckanext/dcat/schemas/dcat_us_full.yaml | 35 ++++- .../dcat_us_3/test_dcat_us_3_profile_parse.py | 127 +++++++++++++++ .../test_dcat_us_3_profile_serialize.py | 144 +++++++++++++++++- ckanext/dcat/tests/shacl/test_shacl.py | 2 + ...kan_full_dataset_dcat_us_vocabularies.json | 14 ++ 6 files changed, 413 insertions(+), 23 deletions(-) diff --git a/ckanext/dcat/profiles/dcat_us_3.py b/ckanext/dcat/profiles/dcat_us_3.py index 491b4328..d3cf6511 100644 --- a/ckanext/dcat/profiles/dcat_us_3.py +++ b/ckanext/dcat/profiles/dcat_us_3.py @@ -1,6 +1,6 @@ from decimal import Decimal, DecimalException -from rdflib import Literal, BNode +from rdflib import Literal, BNode, URIRef from ckanext.dcat.profiles import ( DCAT, @@ -8,6 +8,7 @@ DCT, FOAF, RDF, + RDFS, SKOS, XSD, ) @@ -59,6 +60,84 @@ def graph_from_catalog(self, catalog_dict, catalog_ref): self._graph_from_catalog_base(catalog_dict, catalog_ref) + def _data_dictionary_parse(self, data_dict, subject): + + g = self.g + + for data_dictionary_ref in g.objects(subject, DCATUS.describedBy): + if isinstance(data_dictionary_ref, Literal): + data_dict["data_dictionary"] = str(data_dictionary_ref) + else: + if not isinstance(data_dict.get("data_dictionary"), list): + data_dict["data_dictionary"] = [] + data_dictionary_dict = {} + for item in [ + (DCAT.accessURL, "url"), + (DCT["format"], "format"), + (DCT.license, "license"), + ]: + predicate, key = item + value = self._object_value(data_dictionary_ref, predicate) + if value: + data_dictionary_dict[key] = value + if data_dictionary_dict: + data_dict["data_dictionary"].append(data_dictionary_dict) + + return data_dict + + def _data_dictionary_graph(self, data_dict, subject): + """ + Adds triples related to the data dictionary property of a Datasets + or a Distribution + + TODO: Link somehow to the DataStore data dictionary if that exists + and is public + """ + + g = self.g + + data_dictionary = self._get_dict_value(data_dict, "data_dictionary") + if isinstance(data_dictionary, str): + g.add((subject, DCATUS.describedBy, Literal(data_dictionary))) + elif ( + isinstance(data_dictionary, list) + and len(data_dictionary) + and isinstance(data_dictionary[0], dict) + ): + data_dictionary = data_dictionary[0] + url = data_dictionary.get("url") + if url: + data_dictionary_ref = BNode() + g.add((data_dictionary_ref, RDF.type, DCAT.Distribution)) + self._add_triple_from_dict( + data_dictionary, + data_dictionary_ref, + DCAT.accessURL, + "url", + _type=URIRef, + _class=RDFS.Resource, + ) + if data_dictionary.get("format"): + self._add_triple_from_dict( + data_dictionary, + data_dictionary_ref, + DCT["format"], + "format", + _type=URIRefOrLiteral, + _class=DCT.MediaTypeOrExtent, + ) + # TODO: fallback to dataset / distribution one + if data_dictionary.get("license"): + self._add_triple_from_dict( + data_dictionary, + data_dictionary_ref, + DCT.license, + "license", + _type=URIRefOrLiteral, + _class=DCT.LicenseDocument, + ) + g.add((subject, DCATUS.describedBy, data_dictionary_ref)) + def _parse_dataset_v3_us(self, dataset_dict, dataset_ref): g = self.g @@ -76,19 +155,28 @@ def _parse_dataset_v3_us(self, dataset_dict, dataset_ref): } ) + # Data dictionary + self._data_dictionary_parse(dataset_dict, dataset_ref) + for distribution_ref in self._distributions(dataset_ref): - # Distribution identifier - value = self._object_value(distribution_ref, DCT.identifier) - if value: - for resource_dict in dataset_dict.get("resources", []): - if resource_dict["distribution_ref"] == str(distribution_ref): + for resource_dict in dataset_dict.get("resources", []): + if resource_dict["distribution_ref"] == str(distribution_ref): + + # Distribution identifier + value = self._object_value(distribution_ref, DCT.identifier) + if value: resource_dict["identifier"] = value - # Temporal resolution - value = self._object_value(distribution_ref, DCAT.temporalResolution) - if value: - resource_dict["temporal_resolution"] = value + # Temporal resolution + value = self._object_value( + distribution_ref, DCAT.temporalResolution + ) + if value: + resource_dict["temporal_resolution"] = value + + # Data dictionary + self._data_dictionary_parse(resource_dict, distribution_ref) def _graph_from_dataset_v3_us(self, dataset_dict, dataset_ref): @@ -144,6 +232,9 @@ def add_bounding(predicate, value): ): add_bounding(item[0], item[1]) + # Data dictionary + self._data_dictionary_graph(dataset_dict, dataset_ref) + for resource_dict in dataset_dict.get("resources", []): distribution_ref = CleanedURIRef(resource_uri(resource_dict)) @@ -166,3 +257,6 @@ def add_bounding(predicate, value): "temporal_resolution", _datatype=XSD.duration, ) + + # Data dictionary + self._data_dictionary_graph(resource_dict, distribution_ref) diff --git a/ckanext/dcat/schemas/dcat_us_full.yaml b/ckanext/dcat/schemas/dcat_us_full.yaml index 346c7545..a6c5b7eb 100644 --- a/ckanext/dcat/schemas/dcat_us_full.yaml +++ b/ckanext/dcat/schemas/dcat_us_full.yaml @@ -270,11 +270,20 @@ dataset_fields: validators: ignore_missing scheming_multiple_text help_text: The legislation that mandates the creation or management of the dataset. -#- field_name: hvd_category -# label: HVD Category -# preset: multiple_text -# validators: ignore_missing scheming_multiple_text -# TODO: implement separately as part of wider HVD support +- field_name: data_dictionary + label: Data dictionary + repeating_label: Data dictionary + repeating_once: true + repeating_subfields: + + - field_name: url + label: URL + + - field_name: format + label: Format + + - field_name: license + label: License # Note: if not provided, this will be autogenerated - field_name: uri @@ -438,6 +447,22 @@ resource_fields: help_text: A data service that gives access to the resource. +- field_name: data_dictionary + label: Data dictionary + repeating_label: Data dictionary + repeating_once: true + repeating_subfields: + + - field_name: url + label: URL + + - field_name: format + label: Format + + - field_name: license + label: License + + # Note: if not provided, this will be autogenerated - field_name: uri label: URI diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py index 9a0c75a9..0c09c0c5 100644 --- a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py @@ -153,6 +153,48 @@ def test_e2e_dcat_to_ckan(self): "http://publications.europa.eu/webapi/rdf/sparql" ] + def test_two_distributions(self): + + data = """ + @prefix dcat: . + @prefix dcat-us: . + @prefix dcterms: . + @prefix locn: . + @prefix gsp: . + + + a dcat:Dataset ; + dcterms:title "Dataset 1" ; + dcterms:description "This is a dataset" ; + dcterms:publisher ; + dcat:distribution ; + dcat:distribution + . + + a dcat:Distribution ; + dcterms:title "Resource 1" ; + dcterms:identifier "id1" + . + + a dcat:Distribution ; + dcterms:title "Resource 2" ; + dcterms:identifier "id2" + . + + """ + p = RDFParser() + + p.parse(data, _format="ttl") + + datasets = [d for d in p.datasets()] + + assert len(datasets[0]["resources"]) == 2 + + assert datasets[0]["resources"][0]["name"] == "Resource 1" + assert datasets[0]["resources"][0]["identifier"] == "id1" + assert datasets[0]["resources"][1]["name"] == "Resource 2" + assert datasets[0]["resources"][1]["identifier"] == "id2" + def test_bbox(self): data = """ @@ -188,3 +230,88 @@ def test_bbox(self): assert dataset["bbox"][0]["east"] == "10.3" assert dataset["bbox"][0]["north"] == "50.2" assert dataset["bbox"][0]["south"] == "20.2" + + def test_data_dictionary_dataset(self): + + data = """ + @prefix dcat: . + @prefix dcat-us: . + @prefix dcterms: . + @prefix locn: . + @prefix gsp: . + + + a dcat:Dataset ; + dcterms:title "Dataset 1" ; + dcterms:description "This is a dataset" ; + dcterms:publisher ; + dcat-us:describedBy [ a dcat:Distribution ; + dcterms:format ; + dcterms:license ; + dcat:accessURL ] + . + """ + p = RDFParser() + + p.parse(data, _format="ttl") + + datasets = [d for d in p.datasets()] + + dataset = datasets[0] + assert ( + dataset["data_dictionary"][0]["url"] + == "https://example.org/some-data-dictionary" + ) + assert ( + dataset["data_dictionary"][0]["format"] + == "https://resources.data.gov/vocab/file-type/TODO/JSON" + ) + assert ( + dataset["data_dictionary"][0]["license"] + == "https://resources.data.gov/vocab/license/TODO/CC_BYNC_4_0" + ) + + def test_data_dictionary_distribution(self): + + data = """ + @prefix dcat: . + @prefix dcat-us: . + @prefix dcterms: . + @prefix locn: . + @prefix gsp: . + + + a dcat:Dataset ; + dcterms:title "Dataset 1" ; + dcterms:description "This is a dataset" ; + dcterms:publisher ; + dcat:distribution + . + + + a dcat:Distribution ; + dcat-us:describedBy [ a dcat:Distribution ; + dcterms:format ; + dcterms:license ; + dcat:accessURL ] + . + """ + p = RDFParser() + + p.parse(data, _format="ttl") + + datasets = [d for d in p.datasets()] + + resource = datasets[0]["resources"][0] + assert ( + resource["data_dictionary"][0]["url"] + == "https://example.org/some-data-dictionary" + ) + assert ( + resource["data_dictionary"][0]["format"] + == "https://resources.data.gov/vocab/file-type/TODO/JSON" + ) + assert ( + resource["data_dictionary"][0]["license"] + == "https://resources.data.gov/vocab/license/TODO/CC_BYNC_4_0" + ) diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py index 4ab6ef6e..f521fc95 100644 --- a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py @@ -26,8 +26,6 @@ RDFS, ) -DCAT_AP_PROFILES = ["dcat_us_3"] - @pytest.mark.usefixtures("with_plugins", "clean_db") @pytest.mark.ckan_config("ckan.plugins", "dcat scheming_datasets") @@ -224,15 +222,17 @@ def test_e2e_ckan_to_dcat(self): wkt_geom = wkt.dumps(dataset["spatial_coverage"][0]["geom"], decimals=4) assert self._triple(g, spatial[0][2], LOCN.Geometry, wkt_geom, GSP.wktLiteral) - distribution_ref = self._triple(g, dataset_ref, DCAT.distribution, None)[2] - resource = dataset_dict["resources"][0] - # Alternate identifiers ids = [] for subject in [t[2] for t in g.triples((dataset_ref, ADMS.identifier, None))]: ids.append(str(g.value(subject, SKOS.notation))) assert ids == dataset["alternate_identifier"] + # Resources + + distribution_ref = self._triple(g, dataset_ref, DCAT.distribution, None)[2] + resource = dataset_dict["resources"][0] + # Resources: core fields assert self._triple(g, distribution_ref, DCT.title, resource["name"]) @@ -340,7 +340,7 @@ def test_distribution_identifier(self): ], } - s = RDFSerializer(profiles=DCAT_AP_PROFILES) + s = RDFSerializer() g = s.g dataset_ref = s.graph_from_dataset(dataset_dict) @@ -365,7 +365,7 @@ def test_distribution_identifier_falls_back_to_id(self): ], } - s = RDFSerializer(profiles=DCAT_AP_PROFILES) + s = RDFSerializer() g = s.g dataset_ref = s.graph_from_dataset(dataset_dict) @@ -384,7 +384,7 @@ def test_bbox(self): ], } - s = RDFSerializer(profiles=DCAT_AP_PROFILES) + s = RDFSerializer() g = s.g dataset_ref = s.graph_from_dataset(dataset_dict) @@ -418,3 +418,131 @@ def test_bbox(self): dataset_dict["bbox"][0]["south"], data_type=XSD.decimal, ) + + def test_data_dictionary_dataset(self): + + data_dictionary_dict = { + "url": "https://example.org/some-data-dictionary", + "format": "https://resources.data.gov/vocab/file-type/TODO/JSON", + "license": "https://resources.data.gov/vocab/license/TODO/CC_BYNC_4_0", + } + + dataset_dict = { + "name": "test-dcat-us", + "description": "Test", + "data_dictionary": [data_dictionary_dict], + } + + s = RDFSerializer() + g = s.g + + dataset_ref = s.graph_from_dataset(dataset_dict) + + data_dictionary_ref = [s for s in g.objects(dataset_ref, DCATUS.describedBy)][0] + + assert self._triple( + g, + data_dictionary_ref, + RDF.type, + DCAT.Distribution, + ) + + assert self._triple( + g, + data_dictionary_ref, + DCAT.accessURL, + URIRef(data_dictionary_dict["url"]), + ) + + assert self._triple( + g, + data_dictionary_ref, + DCT["format"], + URIRef(data_dictionary_dict["format"]), + ) + + assert self._triple( + g, + data_dictionary_ref, + DCT.license, + URIRef(data_dictionary_dict["license"]), + ) + + def test_data_dictionary_distribution(self): + + data_dictionary_dict = { + "url": "https://example.org/some-data-dictionary", + "format": "https://resources.data.gov/vocab/file-type/TODO/JSON", + "license": "https://resources.data.gov/vocab/license/TODO/CC_BYNC_4_0", + } + + dataset_dict = { + "name": "test-dcat-us", + "description": "Test", + "resources": [ + { + "id": "2607a002-142a-40b1-8026-96457b70c01d", + "name": "test", + "data_dictionary": [data_dictionary_dict], + } + ], + } + + s = RDFSerializer() + g = s.g + + dataset_ref = s.graph_from_dataset(dataset_dict) + + distribution_ref = [s for s in g.objects(dataset_ref, DCAT.distribution)][0] + + data_dictionary_ref = [ + s for s in g.objects(distribution_ref, DCATUS.describedBy) + ][0] + + assert self._triple( + g, + data_dictionary_ref, + RDF.type, + DCAT.Distribution, + ) + + assert self._triple( + g, + data_dictionary_ref, + DCAT.accessURL, + URIRef(data_dictionary_dict["url"]), + ) + + assert self._triple( + g, + data_dictionary_ref, + DCT["format"], + URIRef(data_dictionary_dict["format"]), + ) + + assert self._triple( + g, + data_dictionary_ref, + DCT.license, + URIRef(data_dictionary_dict["license"]), + ) + + def test_data_dictionary_dataset_string(self): + + dataset_dict = { + "name": "test-dcat-us", + "description": "Test", + "data_dictionary": "https://example.org/some-data-dictionary", + } + + s = RDFSerializer() + g = s.g + + dataset_ref = s.graph_from_dataset(dataset_dict) + + assert self._triple( + g, + dataset_ref, + DCATUS.describedBy, + dataset_dict["data_dictionary"], + ) diff --git a/ckanext/dcat/tests/shacl/test_shacl.py b/ckanext/dcat/tests/shacl/test_shacl.py index cf75faef..c0c23e04 100644 --- a/ckanext/dcat/tests/shacl/test_shacl.py +++ b/ckanext/dcat/tests/shacl/test_shacl.py @@ -216,6 +216,8 @@ def test_validate_dcat_us_3_graph(): graph = graph_from_dataset("ckan_full_dataset_dcat_us_vocabularies.json") graph.serialize(destination="graph.ttl") + + graph.serialize(destination="graph.xml") path = _get_shacl_file_path("dcat-us_3.0_shacl_shapes.ttl") r = validate(graph, shacl_graph=path) conforms, results_graph, results_text = r diff --git a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json index a01f1fa9..f4d7563a 100644 --- a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json +++ b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json @@ -160,6 +160,13 @@ } ], "spatial_resolution_in_meters": 1.5, + "data_dictionary": [ + { + "url": "https://example.org/some-data-dictionary", + "format": "https://resources.data.gov/vocab/file-type/TODO/JSON", + "license": "https://resources.data.gov/vocab/license/TODO/CC_BYNC_4_0" + } + ], "resources": [ { "name": "Resource 1", @@ -187,6 +194,13 @@ "http://id.loc.gov/vocabulary/iso639-1/en", "http://id.loc.gov/vocabulary/iso639-1/es", "http://id.loc.gov/vocabulary/iso639-1/ca" + ], + "data_dictionary": [ + { + "url": "https://example.org/some-data-dictionary", + "format": "https://resources.data.gov/vocab/file-type/TODO/JSON", + "license": "https://resources.data.gov/vocab/license/TODO/CC_BYNC_4_0" + } ] } ] From 82d818697483dc9d297964d814703be82d9fcccb Mon Sep 17 00:00:00 2001 From: amercader Date: Thu, 17 Oct 2024 14:00:59 +0200 Subject: [PATCH 19/26] Fix parsing of statements after #313 --- ckanext/dcat/profiles/base.py | 2 + .../dcat_ap_2/test_scheming_support.py | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/ckanext/dcat/profiles/base.py b/ckanext/dcat/profiles/base.py index 8398e374..5743de5e 100644 --- a/ckanext/dcat/profiles/base.py +++ b/ckanext/dcat/profiles/base.py @@ -221,6 +221,8 @@ def _object_value(self, subject, predicate): # Use first object as fallback if no object with the default language is available elif fallback == "": fallback = str(o) + elif len(list(self.g.objects(o, RDFS.label))): + return str(next(self.g.objects(o, RDFS.label))) else: return str(o) return fallback diff --git a/ckanext/dcat/tests/profiles/dcat_ap_2/test_scheming_support.py b/ckanext/dcat/tests/profiles/dcat_ap_2/test_scheming_support.py index 507c762c..3b1d99f5 100644 --- a/ckanext/dcat/tests/profiles/dcat_ap_2/test_scheming_support.py +++ b/ckanext/dcat/tests/profiles/dcat_ap_2/test_scheming_support.py @@ -790,6 +790,54 @@ def test_e2e_dcat_to_ckan(self): "http://publications.europa.eu/webapi/rdf/sparql" ] + def test_statement_label(self): + data = """ + @prefix dcat: . + @prefix dct: . + @prefix rdfs: . + + + a dcat:Dataset ; + dct:title "Dataset 1" ; + dct:description "This is a dataset" ; + dct:accessRights [ + a dct:RightsStatement; + rdfs:label "Some statement" + ] + . + """ + p = RDFParser() + + p.parse(data, _format="ttl") + datasets = [d for d in p.datasets()] + + dataset = datasets[0] + + assert dataset["notes"] == "This is a dataset" + assert dataset["access_rights"] == "Some statement" + + def test_statement_literal(self): + data = """ + @prefix dcat: . + @prefix dct: . + @prefix rdfs: . + + + a dcat:Dataset ; + dct:title "Dataset 1" ; + dct:description "This is a dataset" ; + dct:accessRights "Some statement" + . + """ + p = RDFParser() + + p.parse(data, _format="ttl") + datasets = [d for d in p.datasets()] + + dataset = datasets[0] + + assert dataset["notes"] == "This is a dataset" + assert dataset["access_rights"] == "Some statement" @pytest.mark.usefixtures("with_plugins", "clean_db", "clean_index") @pytest.mark.ckan_config("ckan.plugins", "dcat scheming_datasets") From b9fa01373b80ea43dc9f85d8ac4f1afed690de12 Mon Sep 17 00:00:00 2001 From: amercader Date: Thu, 17 Oct 2024 14:02:22 +0200 Subject: [PATCH 20/26] Add liability statement property --- ckanext/dcat/profiles/dcat_us_3.py | 14 ++++++++ ckanext/dcat/schemas/dcat_us_full.yaml | 5 +++ .../dcat_us_3/test_dcat_us_3_profile_parse.py | 34 ++++++++++++++++--- .../test_dcat_us_3_profile_serialize.py | 4 +++ ...kan_full_dataset_dcat_us_vocabularies.json | 1 + 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/ckanext/dcat/profiles/dcat_us_3.py b/ckanext/dcat/profiles/dcat_us_3.py index d3cf6511..82194d78 100644 --- a/ckanext/dcat/profiles/dcat_us_3.py +++ b/ckanext/dcat/profiles/dcat_us_3.py @@ -158,6 +158,11 @@ def _parse_dataset_v3_us(self, dataset_dict, dataset_ref): # Data dictionary self._data_dictionary_parse(dataset_dict, dataset_ref) + # Liability statement + value = self._object_value(dataset_ref, DCATUS.liabilityStatement) + if value: + dataset_dict["liability"] = value + for distribution_ref in self._distributions(dataset_ref): for resource_dict in dataset_dict.get("resources", []): @@ -235,6 +240,15 @@ def add_bounding(predicate, value): # Data dictionary self._data_dictionary_graph(dataset_dict, dataset_ref) + # Liability statement + self._add_statement_to_graph( + dataset_dict, + "liability", + dataset_ref, + DCATUS.liabilityStatement, + DCATUS.LiabilityStatement, + ) + for resource_dict in dataset_dict.get("resources", []): distribution_ref = CleanedURIRef(resource_uri(resource_dict)) diff --git a/ckanext/dcat/schemas/dcat_us_full.yaml b/ckanext/dcat/schemas/dcat_us_full.yaml index a6c5b7eb..8b0c0776 100644 --- a/ckanext/dcat/schemas/dcat_us_full.yaml +++ b/ckanext/dcat/schemas/dcat_us_full.yaml @@ -226,6 +226,11 @@ dataset_fields: label: Access rights validators: ignore_missing unicode_safe help_text: Information that indicates whether the dataset is Open Data, has access restrictions or is not public. +- field_name: liability + label: Liability statement + form_snippet: markdown.html + display_snippet: markdown.html + help_text: A statement intended to limit the legal exposure of the data provider by disclaiming warranties or guarantees. - field_name: alternate_identifier label: Other identifier diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py index 0c09c0c5..bb72d455 100644 --- a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py @@ -237,8 +237,6 @@ def test_data_dictionary_dataset(self): @prefix dcat: . @prefix dcat-us: . @prefix dcterms: . - @prefix locn: . - @prefix gsp: . a dcat:Dataset ; @@ -277,8 +275,6 @@ def test_data_dictionary_distribution(self): @prefix dcat: . @prefix dcat-us: . @prefix dcterms: . - @prefix locn: . - @prefix gsp: . a dcat:Dataset ; @@ -288,7 +284,6 @@ def test_data_dictionary_distribution(self): dcat:distribution . - a dcat:Distribution ; dcat-us:describedBy [ a dcat:Distribution ; dcterms:format ; @@ -315,3 +310,32 @@ def test_data_dictionary_distribution(self): resource["data_dictionary"][0]["license"] == "https://resources.data.gov/vocab/license/TODO/CC_BYNC_4_0" ) + + def test_data_liability_statement(self): + + data = """ + @prefix dcat: . + @prefix dcat-us: . + @prefix dcterms: . + @prefix rdfs: . + + + a dcat:Dataset ; + dcterms:title "Dataset 1" ; + dcterms:description "This is a dataset" ; + dcterms:publisher ; + dcat-us:liabilityStatement [ + a dcat-us:LiabilityStatement; + rdfs:label "This dataset is provided 'as-is'." + ] + . + """ + p = RDFParser() + + p.parse(data, _format="ttl") + + datasets = [d for d in p.datasets()] + + dataset = datasets[0] + + assert dataset["liability"] == "This dataset is provided 'as-is'." diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py index f521fc95..2efb6521 100644 --- a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py @@ -228,6 +228,10 @@ def test_e2e_ckan_to_dcat(self): ids.append(str(g.value(subject, SKOS.notation))) assert ids == dataset["alternate_identifier"] + # Liability statement + statement = [s for s in g.objects(dataset_ref, DCATUS.liabilityStatement)][0] + assert self._triple(g, statement, RDFS.label, dataset["liability"]) + # Resources distribution_ref = self._triple(g, dataset_ref, DCAT.distribution, None)[2] diff --git a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json index f4d7563a..b91f2adb 100644 --- a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json +++ b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json @@ -20,6 +20,7 @@ "dcat_type": "http://purl.org/dc/dcmitype/Dataset", "version_notes": "Some version notes", "access_rights": "https://resources.data.gov/vocab/access-right/TODO/PUBLIC", + "liability": "Some statement about liability", "alternate_identifier": [ "https://org1.example.org/datasets/alt-id-1", "https://org2.example.org/datasets/alt-id-2" From 1b267f9bcac68792fafabb761e40f4f186f72bc6 Mon Sep 17 00:00:00 2001 From: amercader Date: Fri, 18 Oct 2024 12:57:21 +0200 Subject: [PATCH 21/26] Support for temporalResolution and spatialResolution in distributions This was already supported in DCAT AP 2, so moved the handling of these two properties to the DCAT AP 2 profile so all profiles support it --- ckanext/dcat/profiles/base.py | 3 ++ ckanext/dcat/profiles/dcat_us_3.py | 9 ---- ckanext/dcat/profiles/euro_dcat_ap_2.py | 52 +++++++++++++++++-- ckanext/dcat/schemas/dcat_ap_full.yaml | 8 +++ ckanext/dcat/schemas/dcat_us_full.yaml | 4 ++ .../test_euro_dcatap_2_profile_parse.py | 5 ++ .../test_euro_dcatap_2_profile_serialize.py | 4 ++ .../dcat_ap_2/test_scheming_support.py | 31 ++++++++--- .../test_euro_dcatap_3_profile_parse.py | 2 + .../test_euro_dcatap_3_profile_serialize.py | 14 +++++ .../dcat_us_3/test_dcat_us_3_profile_parse.py | 1 + .../test_dcat_us_3_profile_serialize.py | 14 +++++ examples/ckan/ckan_full_dataset_dcat_ap.json | 2 + ...kan_full_dataset_dcat_ap_vocabularies.json | 2 + ...kan_full_dataset_dcat_us_vocabularies.json | 2 + examples/dcat/dataset.rdf | 1 + 16 files changed, 134 insertions(+), 20 deletions(-) diff --git a/ckanext/dcat/profiles/base.py b/ckanext/dcat/profiles/base.py index 5743de5e..984450cf 100644 --- a/ckanext/dcat/profiles/base.py +++ b/ckanext/dcat/profiles/base.py @@ -714,6 +714,9 @@ def _read_list_value(self, value): items = value.split(",") else: items = [value] # Normal text value + elif isinstance(value, ((int, float, complex))): + items = [value] # number + return items def _add_spatial_value_to_graph(self, spatial_ref, predicate, value): diff --git a/ckanext/dcat/profiles/dcat_us_3.py b/ckanext/dcat/profiles/dcat_us_3.py index 82194d78..0ed7561e 100644 --- a/ckanext/dcat/profiles/dcat_us_3.py +++ b/ckanext/dcat/profiles/dcat_us_3.py @@ -263,14 +263,5 @@ def add_bounding(predicate, value): _type=URIRefOrLiteral, ) - # Temporal resolution - self._add_triple_from_dict( - resource_dict, - distribution_ref, - DCAT.temporalResolution, - "temporal_resolution", - _datatype=XSD.duration, - ) - # Data dictionary self._data_dictionary_graph(resource_dict, distribution_ref) diff --git a/ckanext/dcat/profiles/euro_dcat_ap_2.py b/ckanext/dcat/profiles/euro_dcat_ap_2.py index b29ac7a4..e5204be1 100644 --- a/ckanext/dcat/profiles/euro_dcat_ap_2.py +++ b/ckanext/dcat/profiles/euro_dcat_ap_2.py @@ -125,11 +125,24 @@ def _parse_dataset_v2(self, dataset_dict, dataset_ref): ("availability", DCATAP.availability), ("compress_format", DCAT.compressFormat), ("package_format", DCAT.packageFormat), + ("temporal_resolution", DCAT.temporalResolution), ): value = self._object_value(distribution, predicate) if value: resource_dict[key] = value + # Spatial resolution in meters + spatial_resolution = self._object_value_float_list( + distribution, DCAT.spatialResolutionInMeters + ) + if spatial_resolution: + value = ( + spatial_resolution[0] + if len(spatial_resolution) == 1 + else json.dumps(spatial_resolution) + ) + resource_dict["spatial_resolution_in_meters"] = value + # Lists for key, predicate in ( ("applicable_legislation", DCATAP.applicableLegislation), @@ -292,7 +305,7 @@ def _graph_from_dataset_v2(self, dataset_dict, dataset_ref): # Resources for resource_dict in dataset_dict.get("resources", []): - distribution = CleanedURIRef(resource_uri(resource_dict)) + distribution_ref = CleanedURIRef(resource_uri(resource_dict)) # Simple values items = [ @@ -313,8 +326,39 @@ def _graph_from_dataset_v2(self, dataset_dict, dataset_ref): ), ] - self._add_triples_from_dict(resource_dict, distribution, items) + self._add_triples_from_dict(resource_dict, distribution_ref, items) + # Temporal resolution + self._add_triple_from_dict( + resource_dict, + distribution_ref, + DCAT.temporalResolution, + "temporal_resolution", + _datatype=XSD.duration, + ) + + # Spatial resolution in meters + spatial_resolution_in_meters = self._read_list_value( + self._get_resource_value(resource_dict, "spatial_resolution_in_meters") + ) + if spatial_resolution_in_meters: + for value in spatial_resolution_in_meters: + try: + self.g.add( + ( + distribution_ref, + DCAT.spatialResolutionInMeters, + Literal(Decimal(value), datatype=XSD.decimal), + ) + ) + except (ValueError, TypeError, DecimalException): + self.g.add( + ( + distribution_ref, + DCAT.spatialResolutionInMeters, + Literal(value), + ) + ) # Lists items = [ ( @@ -325,7 +369,7 @@ def _graph_from_dataset_v2(self, dataset_dict, dataset_ref): ELI.LegalResource, ), ] - self._add_list_triples_from_dict(resource_dict, distribution, items) + self._add_list_triples_from_dict(resource_dict, distribution_ref, items) # Access services access_service_list = resource_dict.get("access_services", []) @@ -346,7 +390,7 @@ def _graph_from_dataset_v2(self, dataset_dict, dataset_ref): # in further profiles access_service_dict["access_service_ref"] = str(access_service_node) - self.g.add((distribution, DCAT.accessService, access_service_node)) + self.g.add((distribution_ref, DCAT.accessService, access_service_node)) self.g.add((access_service_node, RDF.type, DCAT.DataService)) diff --git a/ckanext/dcat/schemas/dcat_ap_full.yaml b/ckanext/dcat/schemas/dcat_ap_full.yaml index ca29a3ac..c50c478c 100644 --- a/ckanext/dcat/schemas/dcat_ap_full.yaml +++ b/ckanext/dcat/schemas/dcat_ap_full.yaml @@ -376,6 +376,14 @@ resource_fields: preset: dcat_date help_text: Most recent date on which the resource was changed, updated or modified. +- field_name: temporal_resolution + label: Temporal resolution + help_text: Minimum time period resolvable in the distribution. + +- field_name: spatial_resolution_in_meters + label: Spatial resolution in meters + help_text: Minimum spatial separation resolvable in the distribution, measured in meters. + - field_name: language label: Language preset: multiple_text diff --git a/ckanext/dcat/schemas/dcat_us_full.yaml b/ckanext/dcat/schemas/dcat_us_full.yaml index 8b0c0776..0b01fc2d 100644 --- a/ckanext/dcat/schemas/dcat_us_full.yaml +++ b/ckanext/dcat/schemas/dcat_us_full.yaml @@ -403,6 +403,10 @@ resource_fields: label: Temporal resolution help_text: Minimum time period resolvable in the distribution. +- field_name: spatial_resolution_in_meters + label: Spatial resolution in meters + help_text: Minimum spatial separation resolvable in the distribution, measured in meters. + - field_name: language label: Language preset: multiple_text diff --git a/ckanext/dcat/tests/profiles/dcat_ap_2/test_euro_dcatap_2_profile_parse.py b/ckanext/dcat/tests/profiles/dcat_ap_2/test_euro_dcatap_2_profile_parse.py index 4ce4a2de..fdda473f 100644 --- a/ckanext/dcat/tests/profiles/dcat_ap_2/test_euro_dcatap_2_profile_parse.py +++ b/ckanext/dcat/tests/profiles/dcat_ap_2/test_euro_dcatap_2_profile_parse.py @@ -68,6 +68,9 @@ def test_dataset_all_fields(self): 2017-02-27 Download WFS Naturräume Geest und Marsch (GML) 2017-03-07T10:00:00 + {temp_res} + {spatial_res} + @@ -134,6 +137,8 @@ def test_dataset_all_fields(self): assert resource['availability'] == dist_availability assert resource['compress_format'] == compress_format assert resource['package_format'] == package_format + assert resource['temporal_resolution'] == temporal_resolution + assert resource['spatial_resolution_in_meters'] == spatial_resolution_in_meters # List values dist_applicable_legislation_list = json.loads(resource.get('applicable_legislation')) diff --git a/ckanext/dcat/tests/profiles/dcat_ap_2/test_euro_dcatap_2_profile_serialize.py b/ckanext/dcat/tests/profiles/dcat_ap_2/test_euro_dcatap_2_profile_serialize.py index 816e9ace..f6e8ae6d 100644 --- a/ckanext/dcat/tests/profiles/dcat_ap_2/test_euro_dcatap_2_profile_serialize.py +++ b/ckanext/dcat/tests/profiles/dcat_ap_2/test_euro_dcatap_2_profile_serialize.py @@ -500,6 +500,8 @@ def test_distribution_fields_literal(self): 'availability': 'EXPERIMENTAL', 'compress_format': 'gzip', 'package_format': 'TAR', + 'temporal_resolution': 'PT15M', + 'spatial_resolution_in_meters': '1.5', 'access_services': json.dumps([ { 'availability': 'AVAILABLE', @@ -542,6 +544,8 @@ def test_distribution_fields_literal(self): assert self._triple(g, distribution, DCATAP.availability, Literal(resource['availability'])) assert self._triple(g, distribution, DCAT.compressFormat, Literal(resource['compress_format'])) assert self._triple(g, distribution, DCAT.packageFormat, Literal(resource['package_format'])) + assert self._triple(g, distribution, DCAT.temporalResolution, Literal(resource['temporal_resolution'], datatype=XSD.duration)) + assert self._triple(g, distribution, DCAT.spatialResolutionInMeters, Literal(resource['spatial_resolution_in_meters'], datatype=XSD.decimal)) # Access services object_list = self._triples(g, distribution, DCAT.accessService, None) diff --git a/ckanext/dcat/tests/profiles/dcat_ap_2/test_scheming_support.py b/ckanext/dcat/tests/profiles/dcat_ap_2/test_scheming_support.py index 3b1d99f5..3c036e2c 100644 --- a/ckanext/dcat/tests/profiles/dcat_ap_2/test_scheming_support.py +++ b/ckanext/dcat/tests/profiles/dcat_ap_2/test_scheming_support.py @@ -79,6 +79,13 @@ def test_e2e_ckan_to_dcat(self): ) assert self._triple(g, dataset_ref, DCT.type, dataset["dcat_type"]) assert self._triple(g, dataset_ref, ADMS.versionNotes, dataset["version_notes"]) + assert self._triple( + g, + dataset_ref, + DCAT.temporalResolution, + dataset["temporal_resolution"], + data_type=XSD.duration, + ) assert self._triple( g, dataset_ref, @@ -102,13 +109,6 @@ def test_e2e_ckan_to_dcat(self): dataset["modified"], data_type=XSD.date, ) - assert self._triple( - g, - dataset_ref, - DCAT.temporalResolution, - dataset["temporal_resolution"], - data_type=XSD.duration, - ) # List fields @@ -326,6 +326,20 @@ def test_e2e_ckan_to_dcat(self): DCAT.downloadURL, URIRef(resource["download_url"]), ) + assert self._triple( + g, + distribution_ref, + DCAT.temporalResolution, + dataset["temporal_resolution"], + data_type=XSD.duration, + ) + assert self._triple( + g, + distribution_ref, + DCAT.spatialResolutionInMeters, + dataset["spatial_resolution_in_meters"], + data_type=XSD.decimal, + ) assert self._triple( g, distribution_ref, DCAT.byteSize, Decimal(resource["size"]), XSD.decimal @@ -750,6 +764,9 @@ def test_e2e_dcat_to_ckan(self): assert resource["rights"] == "Some statement about rights" assert resource["issued"] == "2012-05-11" assert resource["modified"] == "2012-05-01T00:04:06" + assert resource["temporal_resolution"] == "PT15M" + assert resource["spatial_resolution_in_meters"] == 1.5 + assert resource["status"] == "http://purl.org/adms/status/Completed" assert resource["size"] == 12323 assert ( diff --git a/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_parse.py b/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_parse.py index e887a24d..722ca47a 100644 --- a/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_parse.py +++ b/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_parse.py @@ -118,6 +118,8 @@ def test_e2e_dcat_to_ckan(self): assert resource["rights"] == "Some statement about rights" assert resource["issued"] == "2012-05-11" assert resource["modified"] == "2012-05-01T00:04:06" + assert resource["temporal_resolution"] == "PT15M" + assert resource["spatial_resolution_in_meters"] == 1.5 assert resource["status"] == "http://purl.org/adms/status/Completed" assert resource["size"] == 12323 assert ( diff --git a/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_serialize.py b/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_serialize.py index c3a82d33..76cd2c23 100644 --- a/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_serialize.py +++ b/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_serialize.py @@ -300,6 +300,20 @@ def test_e2e_ckan_to_dcat(self): DCAT.downloadURL, URIRef(resource["download_url"]), ) + assert self._triple( + g, + distribution_ref, + DCAT.temporalResolution, + dataset["temporal_resolution"], + data_type=XSD.duration, + ) + assert self._triple( + g, + distribution_ref, + DCAT.spatialResolutionInMeters, + dataset["spatial_resolution_in_meters"], + data_type=XSD.decimal, + ) assert self._triple( g, distribution_ref, DCAT.byteSize, resource["size"], XSD.nonNegativeInteger diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py index bb72d455..fffd4e98 100644 --- a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py @@ -119,6 +119,7 @@ def test_e2e_dcat_to_ckan(self): assert resource["status"] == "http://purl.org/adms/status/Completed" assert resource["size"] == 12323 assert resource["temporal_resolution"] == "PT15M" + assert resource["spatial_resolution_in_meters"] == 1.5 assert ( resource["compress_format"] == "http://www.iana.org/assignments/media-types/application/gzip" diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py index 2efb6521..be5bb58c 100644 --- a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py @@ -276,6 +276,20 @@ def test_e2e_ckan_to_dcat(self): DCAT.downloadURL, URIRef(resource["download_url"]), ) + assert self._triple( + g, + distribution_ref, + DCAT.temporalResolution, + dataset["temporal_resolution"], + data_type=XSD.duration, + ) + assert self._triple( + g, + distribution_ref, + DCAT.spatialResolutionInMeters, + dataset["spatial_resolution_in_meters"], + data_type=XSD.decimal, + ) assert self._triple( g, distribution_ref, DCAT.byteSize, resource["size"], XSD.nonNegativeInteger diff --git a/examples/ckan/ckan_full_dataset_dcat_ap.json b/examples/ckan/ckan_full_dataset_dcat_ap.json index 387d21ba..922e947d 100644 --- a/examples/ckan/ckan_full_dataset_dcat_ap.json +++ b/examples/ckan/ckan_full_dataset_dcat_ap.json @@ -185,6 +185,8 @@ "download_url": "https://example.com/data.csv", "issued": "2024-05-01T01:20:33", "modified": "2024-05-05T09:33:20", + "temporal_resolution": "PT15M", + "spatial_resolution_in_meters": 1.5, "license": "http://creativecommons.org/licenses/by/3.0/", "rights": "Some stament about rights", "language": [ diff --git a/examples/ckan/ckan_full_dataset_dcat_ap_vocabularies.json b/examples/ckan/ckan_full_dataset_dcat_ap_vocabularies.json index 542d1b82..9e0193e9 100644 --- a/examples/ckan/ckan_full_dataset_dcat_ap_vocabularies.json +++ b/examples/ckan/ckan_full_dataset_dcat_ap_vocabularies.json @@ -166,6 +166,8 @@ "download_url": "https://example.com/data.csv", "issued": "2024-05-01T01:20:33", "modified": "2024-05-05T09:33:20", + "temporal_resolution": "PT15M", + "spatial_resolution_in_meters": 1.5, "license": "http://publications.europa.eu/resource/authority/licence/CC_BYNC_4_0", "rights": "http://publications.europa.eu/resource/authority/access-right/PUBLIC", "conforms_to": [ diff --git a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json index b91f2adb..469b846c 100644 --- a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json +++ b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json @@ -186,6 +186,8 @@ "issued": "2024-05-01T01:20:33", "modified": "2024-05-05T09:33:20", "temporal_resolution": "PT15M", + "spatial_resolution_in_meters": 1.5, + "temporal_resolution": "PT15M", "license": "https://resources.data.gov/vocab/license/TODO/CC_BYNC_4_0", "rights": "This dataset is provided by the United States Malacological Commission (USMC)", "conforms_to": [ diff --git a/examples/dcat/dataset.rdf b/examples/dcat/dataset.rdf index 273f4724..7919ca84 100644 --- a/examples/dcat/dataset.rdf +++ b/examples/dcat/dataset.rdf @@ -102,6 +102,7 @@ HTML text/html PT15M + 1.5 http://www.iana.org/assignments/media-types/application/gzip http://publications.europa.eu/resource/authority/file-type/TAR 12323 From 4c18b71b45314cfd21f716541a06b80cad798930 Mon Sep 17 00:00:00 2001 From: amercader Date: Fri, 18 Oct 2024 14:42:13 +0200 Subject: [PATCH 22/26] Add contributor property, support for multiple agents Adds support for dct:contributor to dcat-us, and fixes dct:creator in dcat-ap to allow multiple values. The logic for parsing and serializing has been updated to support multiple instances by default, and we special case dct:publisher which is the only Agent property that has a 0..1 requirement. --- ckanext/dcat/profiles/base.py | 21 +++- ckanext/dcat/profiles/dcat_us_3.py | 10 ++ ckanext/dcat/profiles/euro_dcat_ap_base.py | 27 +++-- .../dcat/profiles/euro_dcat_ap_scheming.py | 110 ++++++++++-------- ckanext/dcat/schemas/dcat_us_full.yaml | 31 +++++ .../tests/profiles/base/test_base_profile.py | 4 +- .../dcat_us_3/test_dcat_us_3_profile_parse.py | 49 +++++++- .../test_dcat_us_3_profile_serialize.py | 34 ++++++ ...kan_full_dataset_dcat_us_vocabularies.json | 12 ++ 9 files changed, 228 insertions(+), 70 deletions(-) diff --git a/ckanext/dcat/profiles/base.py b/ckanext/dcat/profiles/base.py index 984450cf..578e8ec4 100644 --- a/ckanext/dcat/profiles/base.py +++ b/ckanext/dcat/profiles/base.py @@ -424,9 +424,10 @@ def _insert_or_update_temporal(self, dataset_dict, key, value): else: dataset_dict["extras"].append({"key": key, "value": value}) - def _agent_details(self, subject, predicate): + def _agents_details(self, subject, predicate, first_only=False): """ - Returns a dict with details about a dct:publisher or dct:creator entity, a foaf:Agent + Returns a list of dicts with details about a foaf:Agent property, e.g. + dct:publisher or dct:creator entity. Both subject and predicate must be rdflib URIRef or BNode objects @@ -444,17 +445,22 @@ def _agent_details(self, subject, predicate): an empty string if they could not be found. """ - agent_details = {} - + agents = [] for agent in self.g.objects(subject, predicate): + agent_details = {} agent_details["uri"] = str(agent) if isinstance(agent, term.URIRef) else "" agent_details["name"] = self._object_value(agent, FOAF.name) agent_details["email"] = self._object_value(agent, FOAF.mbox) + if not agent_details["email"]: + agent_details["email"] = self._without_mailto( + self._object_value(agent, VCARD.hasEmail) + ) agent_details["url"] = self._object_value(agent, FOAF.homepage) agent_details["type"] = self._object_value(agent, DCT.type) agent_details['identifier'] = self._object_value(agent, DCT.identifier) + agents.append(agent_details) - return agent_details + return agents def _contact_details(self, subject, predicate): """ @@ -1154,10 +1160,13 @@ def _extract_catalog_dict(self, catalog_ref): if val: out.append({"key": key, "value": val}) + publishers = self._agents_details(catalog_ref, DCT.publisher) + if publishers: + publisher = publishers[0] out.append( { "key": "source_catalog_publisher", - "value": json.dumps(self._agent_details(catalog_ref, DCT.publisher)), + "value": json.dumps(publisher), } ) return out diff --git a/ckanext/dcat/profiles/dcat_us_3.py b/ckanext/dcat/profiles/dcat_us_3.py index 0ed7561e..f809b900 100644 --- a/ckanext/dcat/profiles/dcat_us_3.py +++ b/ckanext/dcat/profiles/dcat_us_3.py @@ -163,6 +163,13 @@ def _parse_dataset_v3_us(self, dataset_dict, dataset_ref): if value: dataset_dict["liability"] = value + # Contributors + contributors = self._agents_details(dataset_ref, DCT.contributor) + if contributors: + dataset_dict["contributor"] = [] + for contributor in contributors: + dataset_dict["contributor"].append(contributor) + for distribution_ref in self._distributions(dataset_ref): for resource_dict in dataset_dict.get("resources", []): @@ -249,6 +256,9 @@ def add_bounding(predicate, value): DCATUS.LiabilityStatement, ) + # Contributor + self._add_agents(dataset_ref, dataset_dict, "contributor", DCT.contributor) + for resource_dict in dataset_dict.get("resources", []): distribution_ref = CleanedURIRef(resource_uri(resource_dict)) diff --git a/ckanext/dcat/profiles/euro_dcat_ap_base.py b/ckanext/dcat/profiles/euro_dcat_ap_base.py index ad40d988..6a7da138 100644 --- a/ckanext/dcat/profiles/euro_dcat_ap_base.py +++ b/ckanext/dcat/profiles/euro_dcat_ap_base.py @@ -121,20 +121,23 @@ def _parse_dataset_base(self, dataset_dict, dataset_ref): ) # Publisher - publisher = self._agent_details(dataset_ref, DCT.publisher) - for key in ("uri", "name", "email", "url", "type", "identifier"): - if publisher.get(key): - dataset_dict["extras"].append( - {"key": "publisher_{0}".format(key), "value": publisher.get(key)} - ) + publishers = self._agents_details(dataset_ref, DCT.publisher) + if publishers: + publisher = publishers[0] + for key in ("uri", "name", "email", "url", "type", "identifier"): + if publisher.get(key): + dataset_dict["extras"].append( + {"key": "publisher_{0}".format(key), "value": publisher.get(key)} + ) # Creator - creator = self._agent_details(dataset_ref, DCT.creator) - for key in ("uri", "name", "email", "url", "type", "identifier"): - if creator.get(key): - dataset_dict["extras"].append( - {"key": "creator_{0}".format(key), "value": creator.get(key)} - ) + creators = self._agents_details(dataset_ref, DCT.creator) + for creator in creators: + for key in ("uri", "name", "email", "url", "type", "identifier"): + if creator.get(key): + dataset_dict["extras"].append( + {"key": "creator_{0}".format(key), "value": creator.get(key)} + ) # Temporal start, end = self._time_interval(dataset_ref, DCT.temporal) diff --git a/ckanext/dcat/profiles/euro_dcat_ap_scheming.py b/ckanext/dcat/profiles/euro_dcat_ap_scheming.py index f87c94ca..ea133d88 100644 --- a/ckanext/dcat/profiles/euro_dcat_ap_scheming.py +++ b/ckanext/dcat/profiles/euro_dcat_ap_scheming.py @@ -87,7 +87,7 @@ def _parse_list_value(data_dict, field_name): check_name = new_fields_mapping.get(field_name, field_name) for extra in dataset_dict.get("extras", []): if extra["key"].startswith(f"{check_name}_"): - subfield = extra["key"][extra["key"].index("_") + 1:] + subfield = extra["key"][extra["key"].index("_") + 1 :] if subfield in [ f["field_name"] for f in schema_field["repeating_subfields"] ]: @@ -124,7 +124,11 @@ def _graph_from_dataset_v2_scheming(self, dataset_dict, dataset_ref): Add triples to the graph from new repeating subfields """ contact = dataset_dict.get("contact") - if isinstance(contact, list) and len(contact) and self._not_empty_dict(contact[0]): + if ( + isinstance(contact, list) + and len(contact) + and self._not_empty_dict(contact[0]) + ): for item in contact: contact_uri = item.get("uri") if contact_uri: @@ -150,11 +154,11 @@ def _graph_from_dataset_v2_scheming(self, dataset_dict, dataset_ref): contact_details, VCARD.hasUID, "identifier", - _type=URIRefOrLiteral + _type=URIRefOrLiteral, ) - self._add_agent(dataset_ref, dataset_dict, "publisher", DCT.publisher) - self._add_agent(dataset_ref, dataset_dict, "creator", DCT.creator) + self._add_agents(dataset_ref, dataset_dict, "publisher", DCT.publisher, first_only=True) + self._add_agents(dataset_ref, dataset_dict, "creator", DCT.creator) temporal = dataset_dict.get("temporal_coverage") if ( @@ -172,7 +176,11 @@ def _graph_from_dataset_v2_scheming(self, dataset_dict, dataset_ref): self.g.add((dataset_ref, DCT.temporal, temporal_ref)) spatial = dataset_dict.get("spatial_coverage") - if isinstance(spatial, list) and len(spatial) and self._not_empty_dict(spatial[0]): + if ( + isinstance(spatial, list) + and len(spatial) + and self._not_empty_dict(spatial[0]) + ): for item in spatial: if item.get("uri"): spatial_ref = CleanedURIRef(item["uri"]) @@ -205,55 +213,59 @@ def _graph_from_dataset_v2_scheming(self, dataset_dict, dataset_ref): except ValueError: pass - def _add_agent(self, dataset_ref, dataset_dict, agent_key, rdf_predicate): + def _add_agents( + self, dataset_ref, dataset_dict, agent_key, rdf_predicate, first_only=False + ): """ - Adds an agent (publisher or creator) to the RDF graph. + Adds one or more agents (e.g. publisher or creator) to the RDF graph. :param dataset_ref: The RDF reference of the dataset :param dataset_dict: The dataset dictionary containing agent information - :param agent_key: 'publisher' or 'creator' to specify the agent - :param rdf_predicate: The RDF predicate (DCT.publisher or DCT.creator) + :param agent_key: field name in the CKAN dict (.e.g. "publisher", "creator", etc) + :param rdf_predicate: The RDF predicate (DCT.publisher, DCT.creator, etc) + :first_only: Add the first item found only (used for 0..1 properties) """ agent = dataset_dict.get(agent_key) - if ( - isinstance(agent, list) - and len(agent) - and self._not_empty_dict(agent[0]) - ): - agent = agent[0] - agent_uri = agent.get("uri") - if agent_uri: - agent_ref = CleanedURIRef(agent_uri) - else: - agent_ref = BNode() - - self.g.add((agent_ref, RDF.type, FOAF.Agent)) - self.g.add((dataset_ref, rdf_predicate, agent_ref)) - - self._add_triple_from_dict(agent, agent_ref, FOAF.name, "name") - self._add_triple_from_dict(agent, agent_ref, FOAF.homepage, "url", _type=URIRef) - self._add_triple_from_dict( - agent, - agent_ref, - DCT.type, - "type", - _type=URIRefOrLiteral, - ) - self._add_triple_from_dict( - agent, - agent_ref, - VCARD.hasEmail, - "email", - _type=URIRef, - value_modifier=self._add_mailto, - ) - self._add_triple_from_dict( - agent, - agent_ref, - DCT.identifier, - "identifier", - _type=URIRefOrLiteral - ) + if isinstance(agent, list) and len(agent) and self._not_empty_dict(agent[0]): + agents = [agent[0]] if first_only else agent + + for agent in agents: + + agent_uri = agent.get("uri") + if agent_uri: + agent_ref = CleanedURIRef(agent_uri) + else: + agent_ref = BNode() + + self.g.add((agent_ref, RDF.type, FOAF.Agent)) + self.g.add((dataset_ref, rdf_predicate, agent_ref)) + + self._add_triple_from_dict(agent, agent_ref, FOAF.name, "name") + self._add_triple_from_dict( + agent, agent_ref, FOAF.homepage, "url", _type=URIRef + ) + self._add_triple_from_dict( + agent, + agent_ref, + DCT.type, + "type", + _type=URIRefOrLiteral, + ) + self._add_triple_from_dict( + agent, + agent_ref, + VCARD.hasEmail, + "email", + _type=URIRef, + value_modifier=self._add_mailto, + ) + self._add_triple_from_dict( + agent, + agent_ref, + DCT.identifier, + "identifier", + _type=URIRefOrLiteral, + ) @staticmethod def _not_empty_dict(data_dict): diff --git a/ckanext/dcat/schemas/dcat_us_full.yaml b/ckanext/dcat/schemas/dcat_us_full.yaml index 0b01fc2d..07974798 100644 --- a/ckanext/dcat/schemas/dcat_us_full.yaml +++ b/ckanext/dcat/schemas/dcat_us_full.yaml @@ -104,6 +104,37 @@ dataset_fields: label: Identifier help_text: Unique identifier for the creator, such as an ORCID or ROR ID. +- field_name: contributor + label: Contributor + repeating_label: Contributor + repeating_subfields: + + - field_name: uri + label: URI + help_text: URI of the contributor, if available. + + - field_name: name + label: Name + help_text: Name of the entity or person who contributed to the dataset. + + - field_name: email + label: Email + display_snippet: email.html + help_text: Contact email of the contributor. + + - field_name: url + label: URL + display_snippet: link.html + help_text: URL for more information about the contributor. + + - field_name: type + label: Type + help_text: Type of contributor (e.g., Organization, Person). + + - field_name: identifier + label: Identifier + help_text: Unique identifier for the contributor, such as an ORCID or ROR ID. + - field_name: license_id label: License form_snippet: license.html diff --git a/ckanext/dcat/tests/profiles/base/test_base_profile.py b/ckanext/dcat/tests/profiles/base/test_base_profile.py index 9b341efc..b5d03064 100644 --- a/ckanext/dcat/tests/profiles/base/test_base_profile.py +++ b/ckanext/dcat/tests/profiles/base/test_base_profile.py @@ -660,7 +660,7 @@ def test_publisher_foaf(self): p = RDFProfile(g) - publisher = p._agent_details(URIRef('http://example.org'), DCT.publisher) + publisher = p._agents_details(URIRef('http://example.org'), DCT.publisher)[0] assert publisher['uri'] == 'http://orgs.vocab.org/some-org' assert publisher['name'] == 'Publishing Organization for dataset 1' @@ -688,7 +688,7 @@ def test_publisher_ref(self): p = RDFProfile(g) - publisher = p._agent_details(URIRef('http://example.org'), DCT.publisher) + publisher = p._agents_details(URIRef('http://example.org'), DCT.publisher)[0] assert publisher['uri'] == 'http://orgs.vocab.org/some-org' diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py index fffd4e98..6394cc36 100644 --- a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py @@ -93,6 +93,10 @@ def test_e2e_dcat_to_ckan(self): assert dataset["contact"][0]["name"] == "Point of Contact" assert dataset["contact"][0]["email"] == "contact@some.org" + assert dataset["creator"][0]["name"] == "Creating Organization for dataset 1" + assert dataset["creator"][0]["email"] == "creator@example.org" + assert dataset["creator"][0]["url"] == "http://example.org" + assert ( dataset["publisher"][0]["name"] == "Publishing Organization for dataset 1" ) @@ -102,6 +106,7 @@ def test_e2e_dcat_to_ckan(self): dataset["publisher"][0]["type"] == "http://purl.org/adms/publishertype/NonProfitOrganisation" ) + assert dataset["temporal_coverage"][0]["start"] == "1905-03-01" assert dataset["temporal_coverage"][0]["end"] == "2013-01-05" @@ -324,7 +329,6 @@ def test_data_liability_statement(self): a dcat:Dataset ; dcterms:title "Dataset 1" ; dcterms:description "This is a dataset" ; - dcterms:publisher ; dcat-us:liabilityStatement [ a dcat-us:LiabilityStatement; rdfs:label "This dataset is provided 'as-is'." @@ -340,3 +344,46 @@ def test_data_liability_statement(self): dataset = datasets[0] assert dataset["liability"] == "This dataset is provided 'as-is'." + + def test_contributors(self): + + data = """ + @prefix dcat: . + @prefix dcat-us: . + @prefix dcterms: . + @prefix foaf: . + @prefix vcard: . + + + a dcat:Dataset ; + dcterms:title "Dataset 1" ; + dcterms:description "This is a dataset" ; + dcterms:contributor ; + dcterms:contributor [ + a foaf:Agent; + foaf:name "Test Contributor 1" ; + vcard:hasEmail ; + foaf:homepage ; + ] + . + + a foaf:Person; + foaf:name "John Doe" ; + . + """ + p = RDFParser() + + p.parse(data, _format="ttl") + + datasets = [d for d in p.datasets()] + + dataset = datasets[0] + + assert dataset["contributor"][0]["name"] == "John Doe" + assert ( + dataset["contributor"][0]["uri"] == "https://orcid.org/0000-0002-0693-466X" + ) + + assert dataset["contributor"][1]["name"] == "Test Contributor 1" + assert dataset["contributor"][1]["email"] == "contributor1@example.org" + assert dataset["contributor"][1]["url"] == "https://example.org" diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py index be5bb58c..978f7973 100644 --- a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py @@ -177,6 +177,40 @@ def test_e2e_ckan_to_dcat(self): URIRef(dataset_dict["publisher"][0]["url"]), ) + contributor = [t for t in g.triples((dataset_ref, DCT.contributor, None))] + + assert len(contributor) == 2 + assert self._triple( + g, contributor[0][2], FOAF.name, dataset_dict["contributor"][0]["name"] + ) + assert self._triple( + g, + contributor[0][2], + VCARD.hasEmail, + URIRef("mailto:" + dataset_dict["contributor"][0]["email"]), + ) + assert self._triple( + g, + contributor[0][2], + FOAF.homepage, + URIRef(dataset_dict["contributor"][0]["url"]), + ) + assert self._triple( + g, contributor[1][2], FOAF.name, dataset_dict["contributor"][1]["name"] + ) + assert self._triple( + g, + contributor[1][2], + VCARD.hasEmail, + URIRef("mailto:" + dataset_dict["contributor"][1]["email"]), + ) + assert self._triple( + g, + contributor[1][2], + FOAF.homepage, + URIRef(dataset_dict["contributor"][1]["url"]), + ) + temporal = [t for t in g.triples((dataset_ref, DCT.temporal, None))] assert len(temporal) == len(dataset["temporal_coverage"]) diff --git a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json index 469b846c..17cc5a4d 100644 --- a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json +++ b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json @@ -68,6 +68,18 @@ "url": "https://example.org" } ], + "contributor": [ + { + "name": "Test Contributor 1", + "email": "contributor1@example.org", + "url": "https://example.org" + }, + { + "name": "Test Contributor 2", + "email": "contributor2@example.org", + "url": "https://example.org" + } + ], "temporal_coverage": [ { "start": "1905-03-01", From 21d53ea42e97473d28bae664eb642275becb81d6 Mon Sep 17 00:00:00 2001 From: amercader Date: Fri, 18 Oct 2024 15:21:37 +0200 Subject: [PATCH 23/26] Add properties for character encoding, purpose and usage --- ckanext/dcat/profiles/__init__.py | 1 + ckanext/dcat/profiles/base.py | 2 + ckanext/dcat/profiles/dcat_us_3.py | 36 ++++++++++++- ckanext/dcat/schemas/dcat_us_full.yaml | 16 ++++++ .../dcat_us_3/test_dcat_us_3_profile_parse.py | 50 +++++++++++++++++++ .../test_dcat_us_3_profile_serialize.py | 19 ++++++- ...kan_full_dataset_dcat_us_vocabularies.json | 7 +++ examples/dcat/dataset.rdf | 2 + 8 files changed, 130 insertions(+), 3 deletions(-) diff --git a/ckanext/dcat/profiles/__init__.py b/ckanext/dcat/profiles/__init__.py index 6eff474e..6d30a244 100644 --- a/ckanext/dcat/profiles/__init__.py +++ b/ckanext/dcat/profiles/__init__.py @@ -1,5 +1,6 @@ from .base import RDFProfile, CleanedURIRef from .base import ( + CNT, RDF, XSD, SKOS, diff --git a/ckanext/dcat/profiles/base.py b/ckanext/dcat/profiles/base.py index 578e8ec4..effe5d6b 100644 --- a/ckanext/dcat/profiles/base.py +++ b/ckanext/dcat/profiles/base.py @@ -13,6 +13,7 @@ from ckanext.dcat.utils import DCAT_EXPOSE_SUBCATALOGS from ckanext.dcat.validators import is_year, is_year_month, is_date +CNT = Namespace("http://www.w3.org/2011/content#") DCT = Namespace("http://purl.org/dc/terms/") DCAT = Namespace("http://www.w3.org/ns/dcat#") DCATAP = Namespace("http://data.europa.eu/r5r/") @@ -28,6 +29,7 @@ SPDX = Namespace("http://spdx.org/rdf/terms#") namespaces = { + "cnt": CNT, "dct": DCT, "dcat": DCAT, "dcatap": DCATAP, diff --git a/ckanext/dcat/profiles/dcat_us_3.py b/ckanext/dcat/profiles/dcat_us_3.py index f809b900..c2bd2ae8 100644 --- a/ckanext/dcat/profiles/dcat_us_3.py +++ b/ckanext/dcat/profiles/dcat_us_3.py @@ -1,8 +1,10 @@ -from decimal import Decimal, DecimalException +import json +from decimal import DecimalException from rdflib import Literal, BNode, URIRef from ckanext.dcat.profiles import ( + CNT, DCAT, DCATUS, DCT, @@ -170,6 +172,15 @@ def _parse_dataset_v3_us(self, dataset_dict, dataset_ref): for contributor in contributors: dataset_dict["contributor"].append(contributor) + # List fields + for key, predicate in ( + ("purpose", DCATUS.purpose), + ("usage", SKOS.scopeNote), + ): + values = self._object_value_list(dataset_ref, predicate) + if values: + dataset_dict[key] = values + for distribution_ref in self._distributions(dataset_ref): for resource_dict in dataset_dict.get("resources", []): @@ -187,6 +198,13 @@ def _parse_dataset_v3_us(self, dataset_dict, dataset_ref): if value: resource_dict["temporal_resolution"] = value + # Character encoding + value = self._object_value( + distribution_ref, CNT.characterEncoding + ) + if value: + resource_dict["character_encoding"] = value + # Data dictionary self._data_dictionary_parse(resource_dict, distribution_ref) @@ -259,6 +277,13 @@ def add_bounding(predicate, value): # Contributor self._add_agents(dataset_ref, dataset_dict, "contributor", DCT.contributor) + # Lists + items = [ + ("purpose", DCATUS.purpose, None, Literal), + ("usage", SKOS.scopeNote, None, Literal), + ] + self._add_list_triples_from_dict(dataset_dict, dataset_ref, items) + for resource_dict in dataset_dict.get("resources", []): distribution_ref = CleanedURIRef(resource_uri(resource_dict)) @@ -275,3 +300,12 @@ def add_bounding(predicate, value): # Data dictionary self._data_dictionary_graph(resource_dict, distribution_ref) + + # Character encoding + self._add_triple_from_dict( + resource_dict, + distribution_ref, + CNT.characterEncoding, + "character_encoding", + _type=Literal, + ) diff --git a/ckanext/dcat/schemas/dcat_us_full.yaml b/ckanext/dcat/schemas/dcat_us_full.yaml index 07974798..0393586d 100644 --- a/ckanext/dcat/schemas/dcat_us_full.yaml +++ b/ckanext/dcat/schemas/dcat_us_full.yaml @@ -257,6 +257,7 @@ dataset_fields: label: Access rights validators: ignore_missing unicode_safe help_text: Information that indicates whether the dataset is Open Data, has access restrictions or is not public. + - field_name: liability label: Liability statement form_snippet: markdown.html @@ -282,6 +283,17 @@ dataset_fields: help_text: Language or languages of the dataset. # TODO: language form snippet / validator / graph +- field_name: purpose + label: Purpose + preset: multiple_text + validators: ignore_missing scheming_multiple_text + help_text: The purpose of the dataset + +- field_name: usage + label: Usage note + preset: multiple_text + validators: ignore_missing scheming_multiple_text + - field_name: documentation label: Documentation preset: multiple_text @@ -354,6 +366,10 @@ resource_fields: validators: if_empty_guess_format ignore_missing unicode_safe help_text: Media type for this format. If not provided it will be guessed. +- field_name: character_encoding + label: Character encoding + help_text: Character encoding of the file as described by IANA. + - field_name: compress_format label: Compress format help_text: The format of the file in which the data is contained in a compressed form. diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py index 6394cc36..aa7de0de 100644 --- a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_parse.py @@ -123,6 +123,7 @@ def test_e2e_dcat_to_ckan(self): assert resource["modified"] == "2012-05-01T00:04:06" assert resource["status"] == "http://purl.org/adms/status/Completed" assert resource["size"] == 12323 + assert resource["character_encoding"] == "UTF-8" assert resource["temporal_resolution"] == "PT15M" assert resource["spatial_resolution_in_meters"] == 1.5 assert ( @@ -387,3 +388,52 @@ def test_contributors(self): assert dataset["contributor"][1]["name"] == "Test Contributor 1" assert dataset["contributor"][1]["email"] == "contributor1@example.org" assert dataset["contributor"][1]["url"] == "https://example.org" + + def test_usage_note(self): + + data = """ + @prefix dcat: . + @prefix dcat-us: . + @prefix dcterms: . + @prefix skos: . + + + a dcat:Dataset ; + dcterms:title "Dataset 1" ; + dcterms:description "This is a dataset" ; + skos:scopeNote "Some statement about usage" + . + """ + p = RDFParser() + + p.parse(data, _format="ttl") + + datasets = [d for d in p.datasets()] + + dataset = datasets[0] + + assert dataset["usage"] == ["Some statement about usage"] + + def test_purpose(self): + + data = """ + @prefix dcat: . + @prefix dcat-us: . + @prefix dcterms: . + + + a dcat:Dataset ; + dcterms:title "Dataset 1" ; + dcterms:description "This is a dataset" ; + dcat-us:purpose "Some statement about purpose" + . + """ + p = RDFParser() + + p.parse(data, _format="ttl") + + datasets = [d for d in p.datasets()] + + dataset = datasets[0] + + assert dataset["purpose"] == ["Some statement about purpose"] diff --git a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py index 978f7973..3c99b89b 100644 --- a/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py +++ b/ckanext/dcat/tests/profiles/dcat_us_3/test_dcat_us_3_profile_serialize.py @@ -11,6 +11,7 @@ from ckanext.dcat import utils from ckanext.dcat.profiles import ( + CNT, DCAT, DCATUS, DCT, @@ -133,6 +134,14 @@ def test_e2e_ckan_to_dcat(self): self._triples_list_values(g, dataset_ref, DCT.isReferencedBy) == dataset["is_referenced_by"] ) + assert ( + self._triples_list_values(g, dataset_ref, DCATUS.purpose) + == dataset["purpose"] + ) + assert ( + self._triples_list_values(g, dataset_ref, SKOS.scopeNote) + == dataset["usage"] + ) # Repeating subfields @@ -314,16 +323,22 @@ def test_e2e_ckan_to_dcat(self): g, distribution_ref, DCAT.temporalResolution, - dataset["temporal_resolution"], + resource["temporal_resolution"], data_type=XSD.duration, ) assert self._triple( g, distribution_ref, DCAT.spatialResolutionInMeters, - dataset["spatial_resolution_in_meters"], + resource["spatial_resolution_in_meters"], data_type=XSD.decimal, ) + assert self._triple( + g, + distribution_ref, + CNT.characterEncoding, + resource["character_encoding"], + ) assert self._triple( g, distribution_ref, DCAT.byteSize, resource["size"], XSD.nonNegativeInteger diff --git a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json index 17cc5a4d..69ff8d6b 100644 --- a/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json +++ b/examples/ckan/ckan_full_dataset_dcat_us_vocabularies.json @@ -38,6 +38,12 @@ "documentation": [ "https://example.org/some-doc.html" ], + "purpose": [ + "Some notes about the purpose of the dataset" + ], + "usage": [ + "Some notes about the usage of the dataset" + ], "conforms_to": [ "https://resources.data.gov/vocab/TODO/ProtocolValue/DCAT_US_3_0" ], @@ -187,6 +193,7 @@ "url": "https://example.com/data.csv", "format": "https://resources.data.gov/vocab/file-type/TODO/CSV", "mimetype": "http://www.iana.org/assignments/media-types/text/csv", + "character_encoding": "UTF-8", "compress_format": "http://www.iana.org/assignments/media-types/application/gzip", "package_format": "http://publications.europa.eu/resource/authority/file-type/TAR", "size": 12323, diff --git a/examples/dcat/dataset.rdf b/examples/dcat/dataset.rdf index 7919ca84..7ab556cb 100644 --- a/examples/dcat/dataset.rdf +++ b/examples/dcat/dataset.rdf @@ -2,6 +2,7 @@ http://www.bgs.ac.uk/gbase/geochemcd/home.html HTML text/html + UTF-8 PT15M 1.5 http://www.iana.org/assignments/media-types/application/gzip From 3f396754aa8cef24278548d01571f1a9fe892319 Mon Sep 17 00:00:00 2001 From: amercader Date: Mon, 21 Oct 2024 13:09:26 +0200 Subject: [PATCH 24/26] Add schema for DCAT US recommended properties --- ckanext/dcat/schemas/dcat_ap_full.yaml | 2 +- ckanext/dcat/schemas/dcat_ap_recommended.yaml | 2 +- ckanext/dcat/schemas/dcat_us_full.yaml | 12 - ckanext/dcat/schemas/dcat_us_recommended.yaml | 256 ++++++++++++++++++ 4 files changed, 258 insertions(+), 14 deletions(-) create mode 100644 ckanext/dcat/schemas/dcat_us_recommended.yaml diff --git a/ckanext/dcat/schemas/dcat_ap_full.yaml b/ckanext/dcat/schemas/dcat_ap_full.yaml index c50c478c..82ee2dfd 100644 --- a/ckanext/dcat/schemas/dcat_ap_full.yaml +++ b/ckanext/dcat/schemas/dcat_ap_full.yaml @@ -1,6 +1,6 @@ scheming_version: 2 dataset_type: dataset -about: Full DCAT AP 2.1 schema +about: Full DCAT AP (2 and 3) schema about_url: http://github.com/ckan/ckanext-dcat dataset_fields: diff --git a/ckanext/dcat/schemas/dcat_ap_recommended.yaml b/ckanext/dcat/schemas/dcat_ap_recommended.yaml index daa7bce4..ca38ab16 100644 --- a/ckanext/dcat/schemas/dcat_ap_recommended.yaml +++ b/ckanext/dcat/schemas/dcat_ap_recommended.yaml @@ -1,6 +1,6 @@ scheming_version: 2 dataset_type: dataset -about: Recommended fields for DCAT AP 2.1 schema +about: Recommended properties for the DCAT AP (2 and 3) schema about_url: http://github.com/ckan/ckanext-dcat dataset_fields: diff --git a/ckanext/dcat/schemas/dcat_us_full.yaml b/ckanext/dcat/schemas/dcat_us_full.yaml index 0393586d..24e8dedd 100644 --- a/ckanext/dcat/schemas/dcat_us_full.yaml +++ b/ckanext/dcat/schemas/dcat_us_full.yaml @@ -312,12 +312,6 @@ dataset_fields: validators: ignore_missing scheming_multiple_text help_text: A related resource, such as a publication, that references, cites, or otherwise points to the dataset. -- field_name: applicable_legislation - label: Applicable legislation - preset: multiple_text - validators: ignore_missing scheming_multiple_text - help_text: The legislation that mandates the creation or management of the dataset. - - field_name: data_dictionary label: Data dictionary repeating_label: Data dictionary @@ -472,12 +466,6 @@ resource_fields: validators: ignore_missing scheming_multiple_text help_text: An established schema to which the described resource conforms. -- field_name: applicable_legislation - label: Applicable legislation - preset: multiple_text - validators: ignore_missing scheming_multiple_text - help_text: The legislation that mandates the creation or management of the resource. - - field_name: access_services label: Access services repeating_label: Access service diff --git a/ckanext/dcat/schemas/dcat_us_recommended.yaml b/ckanext/dcat/schemas/dcat_us_recommended.yaml new file mode 100644 index 00000000..f5aea9b3 --- /dev/null +++ b/ckanext/dcat/schemas/dcat_us_recommended.yaml @@ -0,0 +1,256 @@ +scheming_version: 2 +dataset_type: dataset +about: Recommended properties of the DCAT US schema +about_url: http://github.com/ckan/ckanext-dcat + +dataset_fields: + +- field_name: title + label: Title + preset: title + required: true + help_text: A descriptive title for the dataset. + +- field_name: name + label: URL + preset: dataset_slug + form_placeholder: eg. my-dataset + +- field_name: notes + label: Description + required: true + form_snippet: markdown.html + help_text: A free-text account of the dataset. + +- field_name: tag_string + label: Keywords + preset: tag_string_autocomplete + form_placeholder: eg. economy, mental health, government + help_text: Keywords or tags describing the dataset. Use commas to separate multiple values. + +- field_name: contact + label: Contact points + repeating_label: Contact point + repeating_subfields: + + - field_name: uri + label: URI + + - field_name: name + label: Name + + - field_name: email + label: Email + display_snippet: email.html + help_text: Contact information for enquiries about the dataset. + +- field_name: publisher + label: Publisher + repeating_label: Publisher + repeating_once: true + repeating_subfields: + + - field_name: uri + label: URI + + - field_name: name + label: Name + + - field_name: email + label: Email + display_snippet: email.html + + - field_name: url + label: URL + display_snippet: link.html + + - field_name: type + label: Type + + - field_name: identifier + label: Identifier + help_text: Unique identifier for the publisher, such as a ROR ID. + help_text: Entity responsible for making the dataset available. + +- field_name: license_id + label: License + form_snippet: license.html + help_text: License definitions and additional information can be found at http://opendefinition.org/. + +- field_name: owner_org + label: Organization + preset: dataset_organization + help_text: The CKAN organization the dataset belongs to. + +- field_name: url + label: Landing page + form_placeholder: http://example.com/dataset.json + display_snippet: link.html + help_text: Web page that can be navigated to gain access to the dataset, its distributions and/or additional information. + + # Note: this will fall back to metadata_created if not present +- field_name: issued + label: Release date + preset: dcat_date + help_text: Date of publication of the dataset. + + # Note: this will fall back to metadata_modified if not present +- field_name: modified + label: Modification date + preset: dcat_date + help_text: Most recent date on which the dataset was changed, updated or modified. + + # Note: CKAN will generate a unique identifier for each dataset +- field_name: identifier + label: Identifier + help_text: A unique identifier of the dataset, if not provided it will fall back to CKAN's internal id. + +- field_name: temporal_coverage + label: Temporal coverage + repeating_subfields: + + - field_name: start + label: Start + preset: dcat_date + + - field_name: end + label: End + preset: dcat_date + help_text: The temporal period or periods the dataset covers. + +- field_name: bbox + label: Geographic Bounding Box + repeating_subfields: + + - field_name: west + label: West Longitude + + - field_name: east + label: East Longitude + + - field_name: north + label: North Latitude + + - field_name: south + label: South Latitude + help_text: A geographic bounding box in WGS84 coordinate system (Lat/Long) that describes the spatial extent of the dataset. + +- field_name: spatial_coverage + label: Spatial coverage + repeating_subfields: + + - field_name: uri + label: URI + + - field_name: text + label: Label + + - field_name: geom + label: Geometry + + - field_name: bbox + label: Bounding Box + + - field_name: centroid + label: Centroid + help_text: A geographic region that is covered by the dataset. + +- field_name: theme + label: Theme + preset: multiple_text + validators: ignore_missing scheming_multiple_text + help_text: A category of the dataset. A Dataset may be associated with multiple themes. + +- field_name: data_dictionary + label: Data dictionary + repeating_label: Data dictionary + repeating_once: true + repeating_subfields: + + - field_name: url + label: URL + + - field_name: format + label: Format + + - field_name: license + label: License + +# Note: if not provided, this will be autogenerated +- field_name: uri + label: URI + help_text: An URI for this dataset (if not provided it will be autogenerated). + +# TODO: relation-based properties are not yet included (e.g. is_version_of, source, sample, etc) +# +resource_fields: + +- field_name: url + label: URL + preset: resource_url_upload + +- field_name: name + label: Name + form_placeholder: + help_text: A descriptive title for the resource. + +- field_name: format + label: Format + preset: resource_format_autocomplete + help_text: File format. If not provided it will be guessed. + +- field_name: rights + label: Rights + form_snippet: markdown.html + display_snippet: markdown.html + help_text: Some statement about the rights associated with the resource. + +- field_name: license + label: License + help_text: License in which the resource is made available. If not provided will be inherited from the dataset. + + # Note: this falls back to the standard resource url field +- field_name: access_url + label: Access URL + help_text: URL that gives access to the dataset (defaults to the standard resource URL). + + # Note: this falls back to the standard resource url field +- field_name: download_url + label: Download URL + display_snippet: link.html + help_text: URL that provides a direct link to a downloadable file (defaults to the standard resource URL). + +- field_name: issued + label: Release date + preset: dcat_date + help_text: Date of publication of the resource. + +- field_name: modified + label: Modification date + preset: dcat_date + help_text: Most recent date on which the resource was changed, updated or modified. + +- field_name: temporal_resolution + label: Temporal resolution + help_text: Minimum time period resolvable in the distribution. + +- field_name: data_dictionary + label: Data dictionary + repeating_label: Data dictionary + repeating_once: true + repeating_subfields: + + - field_name: url + label: URL + + - field_name: format + label: Format + + - field_name: license + label: License + + + # Note: if not provided, this will be autogenerated +- field_name: uri + label: URI + help_text: An URI for this resource (if not provided it will be autogenerated). From 49da344c39ecc40869195c77147cccdaf3df50a2 Mon Sep 17 00:00:00 2001 From: amercader Date: Mon, 21 Oct 2024 15:47:56 +0200 Subject: [PATCH 25/26] Update docs to reflect DCAT-US changes --- docs/index.md | 4 ++-- docs/mapping.md | 28 ++++++++++++++++++---------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/docs/index.md b/docs/index.md index de9d0ad5..c3caca01 100644 --- a/docs/index.md +++ b/docs/index.md @@ -103,7 +103,7 @@ Ckanext-dcat is a [CKAN](https://github.com/ckan/ckan) extension that helps data In terms of CKAN features, this extension offers: -* [Pre-built CKAN schemas](getting-started.md#schemas) for common Application Profiles that can be adapted to each site requirements to provide out-of-the-box DCAT support in data portals, including tailored form fields, validation etc. (currently supporting DCAT AP v1, v2, and v3). +* [Pre-built CKAN schemas](getting-started.md#schemas) for common Application Profiles that can be adapted to each site requirements to provide out-of-the-box DCAT support in data portals, including tailored form fields, validation etc. (currently supporting **DCAT-AP** [v1.1](https://joinup.ec.europa.eu/asset/dcat_application_profile/asset_release/dcat-ap-v11), [v2.1](https://joinup.ec.europa.eu/collection/semantic-interoperability-community-semic/solution/dcat-application-profile-data-portals-europe/release/210) and [v3](https://semiceu.github.io/DCAT-AP/releases/3.0.0/) and **DCAT-US** [v3](https://doi-do.github.io/dcat-us/)). * [DCAT Endpoints](endpoints.md) that expose the catalog datasets in different RDF serializations (`dcat` plugin). @@ -114,7 +114,7 @@ In terms of CKAN features, this extension offers: These are implemented internally using: -* A base [mapping](mapping.md) between DCAT and CKAN datasets and viceversa (compatible with [DCAT-AP v1.1](https://joinup.ec.europa.eu/asset/dcat_application_profile/asset_release/dcat-ap-v11), [DCAT-AP v2.1](https://joinup.ec.europa.eu/collection/semantic-interoperability-community-semic/solution/dcat-application-profile-data-portals-europe/release/210) and [DCAT-AP v3](https://semiceu.github.io/DCAT-AP/releases/3.0.0/)). +* A base [mapping](mapping.md) between DCAT and CKAN datasets and viceversa (compatible with **DCAT-AP** [v1.1](https://joinup.ec.europa.eu/asset/dcat_application_profile/asset_release/dcat-ap-v11), [v2.1](https://joinup.ec.europa.eu/collection/semantic-interoperability-community-semic/solution/dcat-application-profile-data-portals-europe/release/210) and [v3](https://semiceu.github.io/DCAT-AP/releases/3.0.0/) and **DCAT-US** [v3](https://doi-do.github.io/dcat-us/)). * An [RDF Parser](profiles.md#rdf-dcat-parser) that allows to read RDF serializations in different formats and extract CKAN dataset dicts, using customizable [profiles](profiles.md#profiles). diff --git a/docs/mapping.md b/docs/mapping.md index 97261b08..d127a1c9 100644 --- a/docs/mapping.md +++ b/docs/mapping.md @@ -6,13 +6,13 @@ the DCAT publisher property with a CKAN dataset author, maintainer or organizati and may depend on a particular instance needs. When mapping from CKAN metadata to DCAT though, there are in some cases fallback fields that are used if the default field is not present (see [RDF Serializer](profiles.md#rdf-dcat-serializer) for more details on this). -This mapping is compatible with [DCAT-AP v1.1](https://joinup.ec.europa.eu/asset/dcat_application_profile/asset_release/dcat-ap-v11), [DCAT-AP v2.1](https://joinup.ec.europa.eu/collection/semantic-interoperability-community-semic/solution/dcat-application-profile-data-portals-europe/release/210) and [DCAT-AP v3](https://semiceu.github.io/DCAT-AP/releases/3.0.0/). It depends on the active profile(s) (see [Profiles](profiles.md#profiles)) which DCAT properties are mapped. +This mapping is compatible with **DCAT-AP** [v1.1](https://joinup.ec.europa.eu/asset/dcat_application_profile/asset_release/dcat-ap-v11), [v2.1](https://joinup.ec.europa.eu/collection/semantic-interoperability-community-semic/solution/dcat-application-profile-data-portals-europe/release/210) and [v3](https://semiceu.github.io/DCAT-AP/releases/3.0.0/) and **DCAT-US** [v3](https://doi-do.github.io/dcat-us/). It depends on the active [profile(s)](profiles.md#profiles) and the fields present in your custom [schema](getting-started.md#schemas) which DCAT properties are mapped. Sites are encouraged to use ckanext-scheming to manage their metadata schema (see [Schemas](getting-started.md#schemas) for all details). This changes in some cases the way metadata is stored internally and presented at the CKAN API level, but should not affect the RDF DCAT output. !!! Note - Fields prefixed with `custom:` are custom metadata fields defined via ckanext-scheming. When using `euro_dcat_ap` + Fields prefixed with `custom:` are custom metadata fields defined via ckanext-scheming. When using `euro_dcat_ap` and `euro_dcat_ap_2` based profiles, these could also be actual extra fields (e.g. `extras=[{"key": "issued", "value": "2024"}]`). It is recommended that site maintainers start to migrate to custom fields by using the `euro_dcat_ap_scheming` profile as this fields are properly validated, can use the scheming snippets etc. See [Schemas](getting-started.md#schemas) for more details. @@ -38,6 +38,9 @@ some cases the way metadata is stored internally and presented at the CKAN API l | dcat:Dataset | dct:accessRights | custom:access_rights | | text | | | dcat:Dataset | foaf:page | custom:documentation | | list | See [Lists](#lists) | | dcat:Dataset | dct:provenance | custom:provenance | | text | | +| dcat:Dataset | dcat-us:liabilityStatement | custom:liability | | text | DCAT-US v3 and higher only +| dcat:Dataset | dcat-us:purpose | custom:purpose | | text | DCAT-US v3 and higher only +| dcat:Dataset | skos:scopeNote | custom:usage | | text | DCAT-US v3 and higher only | dcat:Dataset | dct:type | custom:dcat_type | | text | | | dcat:Dataset | dct:hasVersion | custom:has_version | | list | See [Lists](#lists). It is assumed that these are one or more URIs referring to another dcat:Dataset | | dcat:Dataset | dct:isVersionOf | custom:is_version_of | | list | See [Lists](#lists). It is assumed that these are one or more URIs referring to another dcat:Dataset | @@ -45,22 +48,25 @@ some cases the way metadata is stored internally and presented at the CKAN API l | dcat:Dataset | adms:sample | custom:sample | | list | See [Lists](#lists). It is assumed that these are one or more URIs referring to dcat:Distribution instances | | dcat:Dataset | dct:spatial | custom:spatial_uri | | text | See [Spatial coverage](#spatial-coverage) | | dcat:Dataset | dct:temporal | custom:temporal_start + custom:temporal_end | | text | None, one or both extras can be present | +| dcat:Dataset | dcat-us:geographicBoundingBox | custom:bbox | | list of objects | DCAT-US v3 and higher only +| dcat:Dataset | dcat-us:describedBy | custom:data_dictionary | | list of objects | DCAT-US v3 and higher only | dcat:Dataset | dcat:temporalResolution | custom:temporal_resolution | | list | | | dcat:Dataset | dcat:spatialResolutionInMeters | custom:spatial_resolution_in_meters | | list | | | dcat:Dataset | dct:isReferencedBy | custom:is_referenced_by | | list | | -| dcat:Dataset | dct:publisher | custom:publisher_uri | | text | See [URIs](mapping.md#uris) and [Publisher](#contact-points-and-publisher) | +| dcat:Dataset | dct:publisher | custom:publisher_uri | | list of objects | See [URIs](mapping.md#uris) and [Publisher](#contact-points-and-publisher) | | foaf:Agent | foaf:name | custom:publisher_name | | text | | | foaf:Agent | foaf:mbox | custom:publisher_email | organization:title | text | | | foaf:Agent | foaf:homepage | custom:publisher_url | | text | | | foaf:Agent | dct:type | custom:publisher_type | | text | | -| foaf:Agent | dct:identifier | custom:publisher_id | | text | -| dcat:Dataset | dct:creator | custom:creator_uri | | text | See [URIs](mapping.md#uris) and [Publisher](#contact-points-and-publisher) | +| foaf:Agent | dct:identifier | custom:publisher_id | | text | +| dcat:Dataset | dct:creator | custom:creator_uri | | list of objects | See [URIs](mapping.md#uris) | | foaf:Agent | foaf:name | custom:creator_name | | text | | | foaf:Agent | foaf:mbox | custom:creator_email | organization:title | text | | | foaf:Agent | foaf:homepage | custom:creator_url | | text | | | foaf:Agent | dct:type | custom:creator_type | | text | | -| foaf:Agent | dct:identifier | custom:creator_id | | text | -| dcat:Dataset | dcat:contactPoint | custom:contact_uri | | text | See [URIs](mapping.md#uris) and [Contact points](#contact-points-and-publisher) | +| foaf:Agent | dct:identifier | custom:creator_id | | text | +| dcat:Dataset | dct:contributor | custom:contributor | | list of objects | See [URIs](mapping.md#uris). The object properties are the same than publishers and creators. DCAT-US v3 and higher only | +| dcat:Dataset | dcat:contactPoint | custom:contact_uri | | list of objects | See [URIs](mapping.md#uris) and [Contact points](#contact-points-and-publisher) | | vcard:Kind | vcard:fn | custom:contact_name | maintainer, author | text | | | vcard:Kind | vcard:hasEmail | custom:contact_email | maintainer_email, author_email | text | | | vcard:Kind | vcard:hasUID | custom:contact_identifier | | text | | @@ -84,7 +90,10 @@ some cases the way metadata is stored internally and presented at the CKAN API l | dcat:Distribution | dcatap:availability | resource:availability | | text | | | dcat:Distribution | dcat:compressFormat | resource:compress_format | | text | | | dcat:Distribution | dcat:packageFormat | resource:package_format | | text | | -| dcat:Distribution | dcat:accessService | resource:access_services | | text | | +| dcat:Distribution | cnt:characterEncoding | resource:package_format | | text | DCAT-US v3 and higher only +| dcat:Distribution | dct:identifier | custom:identifier | custom:guid, id | text | DCAT-US v3 and higher only +| dcat:Distribution | dcat-us:describedBy | custom:data_dictionary | | list of objects | DCAT-US v3 and higher only +| dcat:Distribution | dcat:accessService | resource:access_services | | list of objects | | | dcat:DataService | dct:title | access_service:title | | text | | | dcat:DataService | dcat:endpointURL | access_service:endpoint_url | | list | | | dcat:DataService | dcat:endpointDescription | access_service:endpoint_description | | text | | @@ -228,7 +237,7 @@ On the scheming-based ones, these are shown as actual lists: ``` ### Contact points and Publisher -Properties for `dcat:contactPoint` and `dct:publisher` are stored as namespaced extras in the legacy profiles. When using +Properties for `dcat:contactPoint` and `dct:publisher` are stored as namespaced extras in the legacy profiles. When using a scheming-based profile, these are stored as proper objects (and multiple instances are allowed for contact point): ```json @@ -268,7 +277,6 @@ If no `publisher` or `publisher_*` fields are found, the serializers will fall b The following formats for `dct:spatial` are supported by the default [parser](profiles.md#rdf-dcat-parser). Note that the default [serializer](profiles.md#rdf-dcat-serializer) will return the single `dct:spatial` instance form by default. -## RDF DCAT Parser - One `dct:spatial` instance, URI only From 641bcd005becd3e87d3af50db625f45e36c24378 Mon Sep 17 00:00:00 2001 From: amercader Date: Mon, 21 Oct 2024 15:50:59 +0200 Subject: [PATCH 26/26] Make mapping table sortable --- docs/_js/tablesort.js | 6 ++++++ mkdocs.yml | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 docs/_js/tablesort.js diff --git a/docs/_js/tablesort.js b/docs/_js/tablesort.js new file mode 100644 index 00000000..6a5afcf2 --- /dev/null +++ b/docs/_js/tablesort.js @@ -0,0 +1,6 @@ +document$.subscribe(function() { + var tables = document.querySelectorAll("article table:not([class])") + tables.forEach(function(table) { + new Tablesort(table) + }) +}) diff --git a/mkdocs.yml b/mkdocs.yml index b23d142e..4a46afc5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -57,6 +57,9 @@ markdown_extensions: alternate_style: true - pymdownx.snippets +extra_javascript: + - https://unpkg.com/tablesort@5.3.0/dist/tablesort.min.js + - _js/tablesort.js extra_css: - _css/extra.css @@ -73,6 +76,4 @@ nav: - Google Dataset Search: 'google-dataset-search.md' - CLI: 'cli.md' - Configuration reference: 'configuration.md' -# - CHANGELOG: 'https://github.com/ckan/ckanext-dcat/blob/master/CHANGELOG.md' - - CHANGELOG: 'changelog.md'