From 38dcbd6f5d9a1d56b9857fffbffe893f6b8ccab9 Mon Sep 17 00:00:00 2001 From: mic29226 <michael.peters@fokus.fraunhofer.de> Date: Wed, 19 Mar 2025 09:36:58 +0100 Subject: [PATCH] finalized the widget feature --- .../components/WidgetInput.vue | 45 +- .../config/dcatapde_BFS/format-types.js | 1 + .../config/dcatapde_BFS/prefixes.js | 47 +- .../store/modules/conversionStore.ts | 2 + .../utils/RDFconverter.js | 1189 ++++++++++------- .../utils/general-helper.js | 527 ++++---- .../utils/inputConverter.js | 684 ++++++---- .../OverviewPage/Properties/StringProp.vue | 2 - 8 files changed, 1421 insertions(+), 1076 deletions(-) diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/WidgetInput.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/WidgetInput.vue index 2f96c43ae..cb0576db3 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/WidgetInput.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/WidgetInput.vue @@ -17,8 +17,9 @@ </div> <!-- Liste Distribution/ Widget/ Report --- leading kann nur 1 sein und auch nur ein widget --> - <button class="ml-3 widgetButton" v-if="widgetType === 'Widget'" - :class="{ activeLeading: activeButton,}" type="button" @click="isLeading"> + <button class="ml-3 widgetButton" v-if="widgetType === 'Widget' || 'LeadingWidget'" + :class="{ activeLeading: activeButton || props.context.value === 'LeadingWidget', }" type="button" + @click="isLeading"> Leading </button> </div> @@ -35,24 +36,21 @@ const props = defineProps({ context: Object }) let disList = ref(getNode('Distributions').value['distributionList']) - - -// Init every dis as distribution - default state -props.context.node.input({ type: 'distribution' }) let activeButton = ref(false) const isLeading = () => { checkForLead() - - console.log(props.context.value['type']); + + // console.log(props.context.value['type']); if (props.context.value['type'] != 'LeadingWidget' && !activeLeadingWidget.value) { - props.context.node.input({ type: 'LeadingWidget' }) + props.context.node.input('LeadingWidget') activeButton.value = !activeButton.value } else { activeLeadingWidget.value = false activeButton.value = false - props.context.node.input({ type: 'Widget' }) + widgetType.value = "Widget" + props.context.node.input('Widget') } } @@ -61,23 +59,40 @@ const matches = ref(['Distribution', 'Widget', 'Report']) let activeLeadingWidget = ref(false) let widgetType = ref('') + +// Init every dis as distribution if there's nothing set - default state + +try { + console.log(props.context.value); + + if (props.context.value != undefined) { + if (props.context.value === 'LeadingWidget') { + widgetType.value = 'Widget' + } else widgetType.value = props.context.value + } else widgetType.value = 'Distribution' +} catch (error) { + +} + + const checkForLead = () => { disList.value = getNode('Distributions').value['distributionList'] - console.log(disList.value, getNode('Distributions').value['distributionList']); + // console.log(disList.value, getNode('Distributions').value['distributionList']); for (let index = 0; index < disList.value.length; index++) { - console.log(disList.value[index]['Mandatory']['pv:DistributionType']['type']); + // console.log(disList.value[index]['Mandatory']['pv:DistributionType']['type']); - if (disList.value[index]['Mandatory']['pv:DistributionType']['type'] === 'LeadingWidget') { + if (disList.value[index]['Mandatory']['pv:DistributionType'] === 'LeadingWidget') { activeLeadingWidget.value = true } + } } const setValue = (item) => { checkForLead() - console.log(getNode('Distributions').value['distributionList'].length); - props.context.node.input({ type: item }) + // console.log(getNode('Distributions').value['distributionList'].length); + props.context.node.input(item) widgetType.value = item } </script> diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde_BFS/format-types.js b/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde_BFS/format-types.js index be71dfde2..0fc89bc6f 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde_BFS/format-types.js +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde_BFS/format-types.js @@ -88,6 +88,7 @@ const typedStrings = { "dct:issued", "dct:modified", "dcat:spatialResolutionInMeters", + "pv:DistributionType", "dcat:byteSize", ], catalogues: [], diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde_BFS/prefixes.js b/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde_BFS/prefixes.js index dcf67d9c1..007e1aa75 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde_BFS/prefixes.js +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde_BFS/prefixes.js @@ -1,25 +1,26 @@ const prefixes = { - adms: "http://www.w3.org/ns/adms#", - dcat: "http://www.w3.org/ns/dcat#", - dcatap: "http://data.europa.eu/r5r/", - dct: "http://purl.org/dc/terms/", - foaf: "http://xmlns.com/foaf/0.1/", - locn: "http://www.w3.org/ns/locn#", - owl: "http://www.w3.org/2002/07/owl#", - odrl: "http://www.w3.org/ns/odrl/2/", - prov: "http://www.w3.org/ns/prov#", - rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", - rdfs: "http://www.w3.org/2000/01/rdf-schema#", - schema: "http://schema.org/", - skos: "http://www.w3.org/2004/02/skos/core#", - spdx: "http://spdx.org/rdf/terms#", - xsd: "http://www.w3.org/2001/XMLSchema#", - vann: "http://purl.org/vocab/vann/", - voaf: "http://purl.org/vocommons/voaf#", - vcard: "http://www.w3.org/2006/vcard/ns#", - time: "http://www.w3.org/2006/time#", - dext: "https://data.europa.eu/ns/ext#", - dcatde: "http://dcat-ap.de/def/dcatde/" + adms: "http://www.w3.org/ns/adms#", + dcat: "http://www.w3.org/ns/dcat#", + dcatap: "http://data.europa.eu/r5r/", + dct: "http://purl.org/dc/terms/", + foaf: "http://xmlns.com/foaf/0.1/", + locn: "http://www.w3.org/ns/locn#", + owl: "http://www.w3.org/2002/07/owl#", + odrl: "http://www.w3.org/ns/odrl/2/", + prov: "http://www.w3.org/ns/prov#", + rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + rdfs: "http://www.w3.org/2000/01/rdf-schema#", + schema: "http://schema.org/", + skos: "http://www.w3.org/2004/02/skos/core#", + spdx: "http://spdx.org/rdf/terms#", + xsd: "http://www.w3.org/2001/XMLSchema#", + vann: "http://purl.org/vocab/vann/", + voaf: "http://purl.org/vocommons/voaf#", + vcard: "http://www.w3.org/2006/vcard/ns#", + time: "http://www.w3.org/2006/time#", + dext: "https://data.europa.eu/ns/ext#", + dcatde: "http://dcat-ap.de/def/dcatde/", + pv: "https://piveau.eu/ns/voc#", }; - -export default prefixes; \ No newline at end of file + +export default prefixes; diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/conversionStore.ts b/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/conversionStore.ts index 726471f40..0ac48f9d0 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/conversionStore.ts +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/conversionStore.ts @@ -106,6 +106,8 @@ const actions = { catalogues: generalHelper.mergeNestedObjects(state.catalogues) }; + // console.log(data); + // merging each distribution object within the overall array of distributions if (has(state.datasets, 'Distributions') && has(state.datasets.Distributions, 'distributionList') && !isEmpty(state.datasets.Distributions.distributionList)) { for (let index = 0; index < state.datasets.Distributions.distributionList.length; index++) { diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/RDFconverter.js b/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/RDFconverter.js index 8315b5054..8ad6fa924 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/RDFconverter.js +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/RDFconverter.js @@ -1,42 +1,62 @@ -import N3 from 'n3'; -import { isEmpty } from 'lodash'; -import { has } from 'lodash'; +import N3 from "n3"; +import { isEmpty } from "lodash"; +import { has } from "lodash"; -import generalDpiConfig from '../config/dpi-spec-config'; +import generalDpiConfig from "../config/dpi-spec-config"; -import generalHelper from './general-helper'; +import generalHelper from "./general-helper"; /** * Converts all properties for given data from form input data into RDF (N-Triples) - * @param {Object} data Data given within an object. Data stored as follows { datasets: {...}, distributions: [{...},...], catalogues: {...}} + * @param {Object} data Data given within an object. Data stored as follows { datasets: {...}, distributions: [{...},...], catalogues: {...}} * @param {String} property Name of property which should be converted (either 'datasets' or 'catalogues') * @returns String of converted data in RDF format (N-Triples) */ -function convertToRDF(data, property, specification) { - - let finishedRDFdata; - - let dpiConfig = specification; - - // writer for adding data as quads - const RDFdata = new N3.Writer({ prefixes: dpiConfig.prefixes, format: 'N-Triples' }); - // datasetURI also needed for distribution creation (add distributionURI to dataset (dcat:distribution)) - const datasetURI = `https://piveau.eu/set/data/${data.datasets.datasetID}`; - - // convert values for datasets/catalogues - convertPropertyValues(RDFdata, data[property], property, '', '', true, datasetURI, dpiConfig); // datasets and catalogues - - // include distribution data into same graph - // differentiation neccessary because datasets also include distributions - if (property === 'datasets') { - // multiple distributions possible -> [{data of distribution 1}, {data of distribution 2}, ...] - for (let index = 0; index < data.distributions.length; index += 1) { - convertPropertyValues(RDFdata, data.distributions[index], 'distributions', '', '', true, datasetURI, dpiConfig); - } +function convertToRDF(data, property, specification) { + let finishedRDFdata; + + let dpiConfig = specification; + + // writer for adding data as quads + const RDFdata = new N3.Writer({ + prefixes: dpiConfig.prefixes, + format: "N-Triples", + }); + // datasetURI also needed for distribution creation (add distributionURI to dataset (dcat:distribution)) + const datasetURI = `https://piveau.eu/set/data/${data.datasets.datasetID}`; + + // convert values for datasets/catalogues + convertPropertyValues( + RDFdata, + data[property], + property, + "", + "", + true, + datasetURI, + dpiConfig + ); // datasets and catalogues + + // include distribution data into same graph + // differentiation neccessary because datasets also include distributions + if (property === "datasets") { + // multiple distributions possible -> [{data of distribution 1}, {data of distribution 2}, ...] + for (let index = 0; index < data.distributions.length; index += 1) { + convertPropertyValues( + RDFdata, + data.distributions[index], + "distributions", + "", + "", + true, + datasetURI, + dpiConfig + ); } + } - RDFdata.end((error, result) => finishedRDFdata = result); - return finishedRDFdata; + RDFdata.end((error, result) => (finishedRDFdata = result)); + return finishedRDFdata; } /** @@ -47,322 +67,464 @@ function convertToRDF(data, property, specification) { * @param {DataFactory} preMainURI (can be undefined) Could be a namedNode or BlankNode containing an URI * @param {DataFactory} preMainType (can be undefined) NamedNode determining the type of the current property (e.g. dcat:Dataset (as object)) * @param {Boolean} setMain Value determining if additional values should be set (type, id, sample...) - * @param {String} datasetURI URI of dataset for use in distribution conversion + * @param {String} datasetURI URI of dataset for use in distribution conversion */ -function convertPropertyValues(RDFdataset, data, property, preMainURI, preMainType, setMain, datasetURI, dpiConfig) { - - const formatTypes = dpiConfig.formatTypes; - - // method can be called recursively for nested properties - // need to access id of parent node for later use as subject -> provide via method parameters (preMainURI & preMainType) - let mainURI; - let mainType; - - // parent method can be called recursively for nested values - // if called on non-nested values a overall id and type muste be set (setMain -> true) - if (setMain) { - if (property === 'datasets') { - mainType = generalHelper.addNamespace('dcat:Dataset', dpiConfig); - mainURI = N3.DataFactory.namedNode(datasetURI); // datasetID should never be empty because of frontend checking - } else if (property === 'catalogues') { - mainType = generalHelper.addNamespace('dcat:Catalog', dpiConfig); - mainURI = N3.DataFactory.namedNode(`https://piveau.eu/set/data/${data.datasetID}`); // datasetID should never be empty because of frontend checking - } else { - mainType = generalHelper.addNamespace('dcat:Distribution', dpiConfig); - const randomId = generalHelper.makeId(10); - // distribution id can be random, will be overwritten by backend on saving data - mainURI = N3.DataFactory.namedNode(`https://piveau.eu/set/data/${randomId}`); - } - - // parent method can be called recursively to convert nested values - // but setting the overal type and id of a dataset/catalogue is only required once at the beginning - // -> only set additional properties when setMain === true - setAdditionalProperties(RDFdataset, data, mainURI, mainType, property, datasetURI, dpiConfig); +function convertPropertyValues( + RDFdataset, + data, + property, + preMainURI, + preMainType, + setMain, + datasetURI, + dpiConfig +) { + + console.log( RDFdataset, + data, + property, + preMainURI, + preMainType, + setMain, + datasetURI, + dpiConfig); + + const formatTypes = dpiConfig.formatTypes; + + // method can be called recursively for nested properties + // need to access id of parent node for later use as subject -> provide via method parameters (preMainURI & preMainType) + let mainURI; + let mainType; + + // parent method can be called recursively for nested values + // if called on non-nested values a overall id and type muste be set (setMain -> true) + if (setMain) { + if (property === "datasets") { + mainType = generalHelper.addNamespace("dcat:Dataset", dpiConfig); + mainURI = N3.DataFactory.namedNode(datasetURI); // datasetID should never be empty because of frontend checking + } else if (property === "catalogues") { + mainType = generalHelper.addNamespace("dcat:Catalog", dpiConfig); + mainURI = N3.DataFactory.namedNode( + `https://piveau.eu/set/data/${data.datasetID}` + ); // datasetID should never be empty because of frontend checking } else { - // called on nested properties with already given URI and type which should used in the following conversion process - mainURI = preMainURI; - mainType = preMainType; + mainType = generalHelper.addNamespace("dcat:Distribution", dpiConfig); + const randomId = generalHelper.makeId(10); + // distribution id can be random, will be overwritten by backend on saving data + mainURI = N3.DataFactory.namedNode( + `https://piveau.eu/set/data/${randomId}` + ); } - // distributions may have download URLs, if no downloadURL is provided -> provided accessUrls will be also set as downloadUrls - // accessUrl is a required property and therefore always provided (made sure by the frontend) - // const downloadUrlsProvided = has(data, 'dcat:downloadURL') && !isEmpty(data['dcat:downloadURL']) && data['dcat:downloadURL'].map(el => !isEmpty(el['@id'])).reduce((a, b) => b); - - // loop trough all keys within data object and convert values (or nested values) to RDF - const valueKeys = Object.keys(data); - for (let index = 0; index < valueKeys.length; index += 1) { - const key = valueKeys[index]; // key format: either a normal name for special properties (e.g. datasetID) or namespaced keys (e.g. dct:title) - - if(generalHelper.propertyHasValue(data[key])) { - // all properties are sorted by their format (see .../data-provider-interface/config/format-types.js) - // depending on the format the corresponding conversion-method is used, writing the result to the overall RDF-writer - if (formatTypes.singularString[property].includes(key)) { - convertSingularString(RDFdataset, mainURI, data, key, dpiConfig); - } else if (formatTypes.singularURI[property].includes(key)) { - convertSingularURI(RDFdataset, mainURI, data, key, dpiConfig); - } else if (formatTypes.multipleURI[property].includes(key)) { - // if no dowloadURL is provided, set accessUrls as downloadUrls - // if (!downloadUrlsProvided && key === 'dcat:accessURL') { - // // copy accessurl array to donwloadurl array and convert data - - // data['dcat:downloadURL'] = cloneDeep(data['dcat:accessURL']); - // convertMultipleURI(RDFdataset, mainURI, data, 'dcat:downloadURL', property, dpiConfig); - // } - - convertMultipleURI(RDFdataset, mainURI, data, key, property, dpiConfig); - } else if (formatTypes.typedStrings[property].includes(key)) { - convertTypedString(RDFdataset, mainURI, data, key, dpiConfig); - } else if (formatTypes.multilingualStrings[property].includes(key)) { - convertMultilingual(RDFdataset, mainURI, data, key, dpiConfig); - } else if (formatTypes.groupedProperties[property].includes(key)) { - - // grouped properties are properties provided by the form which consist of multiple properties (e.g contactPoint) - // the properties values are stored within an object located within an array - // for repeatable properties there are multiple objects in this array, otherwise there is just one - - let actualData; - // vcard:hasAdress is an object as well as dct:creator and skos:notation - if (key === 'vcard:hasAddress' || key === 'dct:creator' || key === 'skos:notation' || key === 'spdx:checksum' ) actualData = [data[key]]; - else actualData = data[key]; - - // looping trough all existing objects within the array - for (let groupId = 0; groupId < actualData.length; groupId += 1) { - let currentGroupData = actualData[groupId]; - - if (!isEmpty(currentGroupData)) { - if (key === 'skos:notation') { - // property skos:notation work a little bit different then other properties - // the form provides a value and a type from two seperated fields ({'@value': '...', '@type': '...'}) - // the resulting RDF should merge these values into a typed literal (value^^type) - if (has(currentGroupData, '@value') && !isEmpty(currentGroupData['@value'])) { - let notationValue; - - // if a type is given, use to form typed literal - // if no type is given, only use value to create literal - if (has(currentGroupData, '@type') && !isEmpty(currentGroupData['@type'])) { // typed literal - notationValue = N3.DataFactory.literal(currentGroupData['@value'], N3.DataFactory.namedNode(currentGroupData['@type'].resource)); - } else { // literal - notationValue = N3.DataFactory.literal(currentGroupData['@value']); - } - - // add type for adms:identifier - RDFdataset.addQuad(N3.DataFactory.quad( - mainURI, - N3.DataFactory.namedNode(generalHelper.addNamespace('rdf:type', dpiConfig)), - N3.DataFactory.namedNode(generalHelper.addNamespace('adms:Identifier', dpiConfig)) - )) - - // save quadruple with typed or untyped literal - RDFdataset.addQuad(N3.DataFactory.quad( - mainURI, - N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), - notationValue - )) - - // resulting rdf quads should look like this: - // datasetId adms:identifier admsIdentifierUtl - // admsIdentifierUrl rdf:type adms:Identifier - // admsIdentifierUrl skos:notation value^^type - } - } else { - let groupBlankNode; - - // because grouped properties have a list of nested properties we need an initial quadruple stating the parent property - // using a blank node as object which later serves as subject for the nested properties - // RDF example: - // datasetID dct:contactPoint blankNodeId - // blankNodeId foaf:mbox email@exmaple.com - // blankNodeId fn:name InsitutionName ... - - // some form fields provide an URL which should serves as namedNode for other nested values (e.g. conformsTo) - // RDF example: - // datasetID dct:conformsTo conformsToURI - // conformsToURI dct:title conformsTitle - if ((key === 'foaf:page' || key === 'adms:identifier' || key === 'dct:conformsTo') && has(currentGroupData, '@id')) { - groupBlankNode = N3.DataFactory.namedNode(currentGroupData['@id']); - } - // all properties that don't provide an URL serving as namedNode for nested values need to define a blank node - - // page gets type but also has multilingual fields with preseleted langauge - // don't create blank node if there is not data for page beside the preselected language - let emptyPage = false; - - if (key === 'foaf:page') { - - // if page has title and/or description property given, check if there are values given - const hasTitle = has(currentGroupData, 'dct:title'); - const hasDescription = has(currentGroupData, 'dct:description'); - - let hasNoValueKeysTitle = true; - let hasEmptyValueTitle = true; - let hasNoValueKeysDescription = true; - let hasEmptyValueDescription = true; - - if (hasTitle) { - hasNoValueKeysTitle = !currentGroupData['dct:title'].every(el => has(el, '@value')); - hasEmptyValueTitle = currentGroupData['dct:title'].every(el => isEmpty(el['@value'])); - } - - if (hasDescription) { - hasNoValueKeysDescription = !currentGroupData['dct:description'].every(el => has(el, '@value')); - hasEmptyValueDescription = currentGroupData['dct:description'].every(el => isEmpty(el['@value'])); - } - - // page should be handled as empty if: - // no title and/or no description given - // if properties given: no value given or value empty - if ((hasNoValueKeysTitle || hasEmptyValueTitle) && (hasNoValueKeysDescription || hasEmptyValueDescription)) emptyPage = true; - } - - if (!emptyPage) { - if (!groupBlankNode) groupBlankNode = N3.DataFactory.blankNode(''); - - // save inital quadruple using the named or blank node as object - // e.g. datasetId dct:contactPoint blankNode/namedNode - RDFdataset.addQuad(N3.DataFactory.quad( - mainURI, - N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), - groupBlankNode - )) - - // some properties provide additional types - if (has(formatTypes.additionalPropertyTypes, key)) { - RDFdataset.addQuad(N3.DataFactory.quad( - groupBlankNode, - N3.DataFactory.namedNode(generalHelper.addNamespace('rdf:type', dpiConfig)), - N3.DataFactory.namedNode(generalHelper.addNamespace(formatTypes.additionalPropertyTypes[key], dpiConfig)) - )) - } - - // temporal values nested inside another object: "dct:temporal": [{ "dct:temporal": { "dcat:startDate": "...", "dcat:endDate": "..." } }] - if (key === 'dct:temporal') { - if (has(currentGroupData, 'dct:temporal')) { - currentGroupData = currentGroupData['dct:temporal']; - } - } - - // convert all nested values provided by form - convertPropertyValues(RDFdataset, currentGroupData, property, groupBlankNode, mainType, false, dpiConfig, dpiConfig); - } - } - } + // parent method can be called recursively to convert nested values + // but setting the overal type and id of a dataset/catalogue is only required once at the beginning + // -> only set additional properties when setMain === true + setAdditionalProperties( + RDFdataset, + data, + mainURI, + mainType, + property, + datasetURI, + dpiConfig + ); + } else { + // called on nested properties with already given URI and type which should used in the following conversion process + mainURI = preMainURI; + mainType = preMainType; + } + + // distributions may have download URLs, if no downloadURL is provided -> provided accessUrls will be also set as downloadUrls + // accessUrl is a required property and therefore always provided (made sure by the frontend) + // const downloadUrlsProvided = has(data, 'dcat:downloadURL') && !isEmpty(data['dcat:downloadURL']) && data['dcat:downloadURL'].map(el => !isEmpty(el['@id'])).reduce((a, b) => b); + + // loop trough all keys within data object and convert values (or nested values) to RDF + const valueKeys = Object.keys(data); + for (let index = 0; index < valueKeys.length; index += 1) { + const key = valueKeys[index]; // key format: either a normal name for special properties (e.g. datasetID) or namespaced keys (e.g. dct:title) + + if (generalHelper.propertyHasValue(data[key])) { + // all properties are sorted by their format (see .../data-provider-interface/config/format-types.js) + // depending on the format the corresponding conversion-method is used, writing the result to the overall RDF-writer + if (formatTypes.singularString[property].includes(key)) { + convertSingularString(RDFdataset, mainURI, data, key, dpiConfig); + } else if (formatTypes.singularURI[property].includes(key)) { + convertSingularURI(RDFdataset, mainURI, data, key, dpiConfig); + } else if (formatTypes.multipleURI[property].includes(key)) { + // if no dowloadURL is provided, set accessUrls as downloadUrls + // if (!downloadUrlsProvided && key === 'dcat:accessURL') { + // // copy accessurl array to donwloadurl array and convert data + + // data['dcat:downloadURL'] = cloneDeep(data['dcat:accessURL']); + // convertMultipleURI(RDFdataset, mainURI, data, 'dcat:downloadURL', property, dpiConfig); + // } + + convertMultipleURI(RDFdataset, mainURI, data, key, property, dpiConfig); + } else if (formatTypes.typedStrings[property].includes(key)) { + convertTypedString(RDFdataset, mainURI, data, key, dpiConfig); + } else if (formatTypes.multilingualStrings[property].includes(key)) { + convertMultilingual(RDFdataset, mainURI, data, key, dpiConfig); + } else if (formatTypes.groupedProperties[property].includes(key)) { + // grouped properties are properties provided by the form which consist of multiple properties (e.g contactPoint) + // the properties values are stored within an object located within an array + // for repeatable properties there are multiple objects in this array, otherwise there is just one + + let actualData; + // vcard:hasAdress is an object as well as dct:creator and skos:notation + if ( + key === "vcard:hasAddress" || + key === "dct:creator" || + key === "skos:notation" || + key === "spdx:checksum" + ) + actualData = [data[key]]; + else actualData = data[key]; + + // looping trough all existing objects within the array + for (let groupId = 0; groupId < actualData.length; groupId += 1) { + let currentGroupData = actualData[groupId]; + + if (!isEmpty(currentGroupData)) { + if (key === "skos:notation") { + // property skos:notation work a little bit different then other properties + // the form provides a value and a type from two seperated fields ({'@value': '...', '@type': '...'}) + // the resulting RDF should merge these values into a typed literal (value^^type) + if ( + has(currentGroupData, "@value") && + !isEmpty(currentGroupData["@value"]) + ) { + let notationValue; + + // if a type is given, use to form typed literal + // if no type is given, only use value to create literal + if ( + has(currentGroupData, "@type") && + !isEmpty(currentGroupData["@type"]) + ) { + // typed literal + notationValue = N3.DataFactory.literal( + currentGroupData["@value"], + N3.DataFactory.namedNode(currentGroupData["@type"].resource) + ); + } else { + // literal + notationValue = N3.DataFactory.literal( + currentGroupData["@value"] + ); } - } else if (formatTypes.conditionalProperties[property].includes(key)) { - // publisher either is an URI or a group with multiple values (name, homepage, email) - // license either is an URI or a group with multiple values () - if (key === 'dct:publisher' || key === 'dct:license') { - - // data contains either {resource: '...', name: '...'} or object containing other keys - if (has(data[key], 'resource')) { - convertSingularURI(RDFdataset, mainURI, data, key, dpiConfig); - } else { - const groupBlankNode = N3.DataFactory.blankNode(''); - - // some properties provide additional types - if (has(formatTypes.additionalPropertyTypes, key)) { - RDFdataset.addQuad(N3.DataFactory.quad( - groupBlankNode, - N3.DataFactory.namedNode(generalHelper.addNamespace('rdf:type', dpiConfig)), - N3.DataFactory.namedNode(generalHelper.addNamespace(formatTypes.additionalPropertyTypes[key], dpiConfig)) - )) - } - - // save inital quadruple using the named or blank node as object - // e.g. datasetId dct:contactPoint blankNode/namedNode - RDFdataset.addQuad(N3.DataFactory.quad( - mainURI, - N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), - groupBlankNode - )) - - convertPropertyValues(RDFdataset, data[key], property, groupBlankNode, mainType, false, dpiConfig, dpiConfig); - } - + // add type for adms:identifier + RDFdataset.addQuad( + N3.DataFactory.quad( + mainURI, + N3.DataFactory.namedNode( + generalHelper.addNamespace("rdf:type", dpiConfig) + ), + N3.DataFactory.namedNode( + generalHelper.addNamespace("adms:Identifier", dpiConfig) + ) + ) + ); + + // save quadruple with typed or untyped literal + RDFdataset.addQuad( + N3.DataFactory.quad( + mainURI, + N3.DataFactory.namedNode( + generalHelper.addNamespace(key, dpiConfig) + ), + notationValue + ) + ); + + // resulting rdf quads should look like this: + // datasetId adms:identifier admsIdentifierUtl + // admsIdentifierUrl rdf:type adms:Identifier + // admsIdentifierUrl skos:notation value^^type + } + } else { + let groupBlankNode; + + // because grouped properties have a list of nested properties we need an initial quadruple stating the parent property + // using a blank node as object which later serves as subject for the nested properties + // RDF example: + // datasetID dct:contactPoint blankNodeId + // blankNodeId foaf:mbox email@exmaple.com + // blankNodeId fn:name InsitutionName ... + + // some form fields provide an URL which should serves as namedNode for other nested values (e.g. conformsTo) + // RDF example: + // datasetID dct:conformsTo conformsToURI + // conformsToURI dct:title conformsTitle + if ( + (key === "foaf:page" || + key === "adms:identifier" || + key === "dct:conformsTo") && + has(currentGroupData, "@id") + ) { + groupBlankNode = N3.DataFactory.namedNode( + currentGroupData["@id"] + ); + } + // all properties that don't provide an URL serving as namedNode for nested values need to define a blank node + + // page gets type but also has multilingual fields with preseleted langauge + // don't create blank node if there is not data for page beside the preselected language + let emptyPage = false; + + if (key === "foaf:page") { + // if page has title and/or description property given, check if there are values given + const hasTitle = has(currentGroupData, "dct:title"); + const hasDescription = has(currentGroupData, "dct:description"); + + let hasNoValueKeysTitle = true; + let hasEmptyValueTitle = true; + let hasNoValueKeysDescription = true; + let hasEmptyValueDescription = true; + + if (hasTitle) { + hasNoValueKeysTitle = !currentGroupData["dct:title"].every( + (el) => has(el, "@value") + ); + hasEmptyValueTitle = currentGroupData["dct:title"].every( + (el) => isEmpty(el["@value"]) + ); } - } else if (key === 'dcat:temporalResolution') { - // temporal resolution is displayed as group of input forms for each property (year, month, day, ...) - // the form provides the data as following: [ { 'Year': '...', 'Month': '...', ... } ] - // the final format of this property should look like this: P?Y?M?DT?H?M?S - // not all values must be filled and therefore be present -> default behavior if not given: value = 0 - - const resolutionValues = data[key]; - const valueString = `P${resolutionValues.Year ? resolutionValues.Year : 0}Y${resolutionValues.Month ? resolutionValues.Month : 0}M${resolutionValues.Day ? resolutionValues.Day : 0}DT${resolutionValues.Hour ? resolutionValues.Hour : 0}H${resolutionValues.Minute ? resolutionValues.Minute : 0}M${resolutionValues.Second ? resolutionValues.Second : 0}S`; - - // frontend always provides temporalResolution even if there is no value resulting in P0Y0M0DT0H0M0S - // don't save if value is equal to P0Y0M0DT0H0M0S - if (valueString !== "P0Y0M0DT0H0M0S") { - RDFdataset.addQuad(N3.DataFactory.quad( - mainURI, - N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), - N3.DataFactory.literal(valueString, N3.DataFactory.namedNode(generalHelper.addNamespace('xsd:duration', dpiConfig))) - )) - } - } else if (key === 'dct:identifier') { - // form provides data as array of objects with strings: [ { '@value': 'string1' }, { '@value': 'string2' }, ... ] - // create quadruple for each given object in the array - for (let valueId = 0; valueId < data[key].length; valueId += 1) { - const currentValue = data[key][valueId]; - if (has(currentValue, '@value') && !isEmpty(currentValue['@value'])) { - RDFdataset.addQuad(N3.DataFactory.quad( - mainURI, - N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), - N3.DataFactory.literal(currentValue['@value']) - )) - } + + if (hasDescription) { + hasNoValueKeysDescription = !currentGroupData[ + "dct:description" + ].every((el) => has(el, "@value")); + hasEmptyValueDescription = currentGroupData[ + "dct:description" + ].every((el) => isEmpty(el["@value"])); } - } else if (key === 'dct:rights') { - // rights has a static type (RightsStatement) which needs to be added to linked data as additional node - // therefore we need to create an initial quadruple for with 'rights' being the predicate having a blank node - // blank node serves as subject for the following quadruples which contain the type and actual value of the form field - // RDF: - // datasetID dct:rights blankNodeID - // blankNodeId rdf:type RightsStatement - // blankNodeId rdfs:label LabelValue - - // blank node as object for inital quadruple and also as subject for following quadruples - const rightsBlankNode = N3.DataFactory.blankNode(''); - - RDFdataset.addQuad(N3.DataFactory.quad( + + // page should be handled as empty if: + // no title and/or no description given + // if properties given: no value given or value empty + if ( + (hasNoValueKeysTitle || hasEmptyValueTitle) && + (hasNoValueKeysDescription || hasEmptyValueDescription) + ) + emptyPage = true; + } + + if (!emptyPage) { + if (!groupBlankNode) + groupBlankNode = N3.DataFactory.blankNode(""); + + // save inital quadruple using the named or blank node as object + // e.g. datasetId dct:contactPoint blankNode/namedNode + RDFdataset.addQuad( + N3.DataFactory.quad( mainURI, - N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), - rightsBlankNode - )) - - // add additional type declaration - RDFdataset.addQuad(N3.DataFactory.quad( - rightsBlankNode, - N3.DataFactory.namedNode(generalHelper.addNamespace('rdf:type', dpiConfig)), - N3.DataFactory.namedNode(generalHelper.addNamespace('dct:RightsStatement', dpiConfig)) - )) - - // rights is a conditional property and provides either an URI or a string ( { rdfs:label : 'URL/string' } ) - let rightsValue; - - if (data[key]['@type'] === 'url') { - rightsValue = N3.DataFactory.namedNode(data[key]['rdfs:label']); - } else { - rightsValue = N3.DataFactory.literal(data[key]['rdfs:label']); + N3.DataFactory.namedNode( + generalHelper.addNamespace(key, dpiConfig) + ), + groupBlankNode + ) + ); + + // some properties provide additional types + if (has(formatTypes.additionalPropertyTypes, key)) { + RDFdataset.addQuad( + N3.DataFactory.quad( + groupBlankNode, + N3.DataFactory.namedNode( + generalHelper.addNamespace("rdf:type", dpiConfig) + ), + N3.DataFactory.namedNode( + generalHelper.addNamespace( + formatTypes.additionalPropertyTypes[key], + dpiConfig + ) + ) + ) + ); } - // add actual value - RDFdataset.addQuad(N3.DataFactory.quad( - rightsBlankNode, - N3.DataFactory.namedNode(generalHelper.addNamespace('rdfs:label', dpiConfig)), - rightsValue - )) - - } else if (key === 'rdf:type') { - // some properties have additional type information which needs to be added to graph - // e.g contactPoint -> vcard:Individual - RDFdataset.addQuad(N3.DataFactory.quad( - mainURI, - N3.DataFactory.namedNode(generalHelper.addNamespace('rdf:type', dpiConfig)), - N3.DataFactory.namedNode(generalHelper.addNamespace(data[key], dpiConfig)) - )) + // temporal values nested inside another object: "dct:temporal": [{ "dct:temporal": { "dcat:startDate": "...", "dcat:endDate": "..." } }] + if (key === "dct:temporal") { + if (has(currentGroupData, "dct:temporal")) { + currentGroupData = currentGroupData["dct:temporal"]; + } + } + + // convert all nested values provided by form + convertPropertyValues( + RDFdataset, + currentGroupData, + property, + groupBlankNode, + mainType, + false, + dpiConfig, + dpiConfig + ); + } + } + } + } + } else if (formatTypes.conditionalProperties[property].includes(key)) { + // publisher either is an URI or a group with multiple values (name, homepage, email) + // license either is an URI or a group with multiple values () + if (key === "dct:publisher" || key === "dct:license") { + // data contains either {resource: '...', name: '...'} or object containing other keys + if (has(data[key], "resource")) { + convertSingularURI(RDFdataset, mainURI, data, key, dpiConfig); + } else { + const groupBlankNode = N3.DataFactory.blankNode(""); + + // some properties provide additional types + if (has(formatTypes.additionalPropertyTypes, key)) { + RDFdataset.addQuad( + N3.DataFactory.quad( + groupBlankNode, + N3.DataFactory.namedNode( + generalHelper.addNamespace("rdf:type", dpiConfig) + ), + N3.DataFactory.namedNode( + generalHelper.addNamespace( + formatTypes.additionalPropertyTypes[key], + dpiConfig + ) + ) + ) + ); } - } + + // save inital quadruple using the named or blank node as object + // e.g. datasetId dct:contactPoint blankNode/namedNode + RDFdataset.addQuad( + N3.DataFactory.quad( + mainURI, + N3.DataFactory.namedNode( + generalHelper.addNamespace(key, dpiConfig) + ), + groupBlankNode + ) + ); + + convertPropertyValues( + RDFdataset, + data[key], + property, + groupBlankNode, + mainType, + false, + dpiConfig, + dpiConfig + ); + } + } + } else if (key === "dcat:temporalResolution") { + // temporal resolution is displayed as group of input forms for each property (year, month, day, ...) + // the form provides the data as following: [ { 'Year': '...', 'Month': '...', ... } ] + // the final format of this property should look like this: P?Y?M?DT?H?M?S + // not all values must be filled and therefore be present -> default behavior if not given: value = 0 + + const resolutionValues = data[key]; + const valueString = `P${ + resolutionValues.Year ? resolutionValues.Year : 0 + }Y${resolutionValues.Month ? resolutionValues.Month : 0}M${ + resolutionValues.Day ? resolutionValues.Day : 0 + }DT${resolutionValues.Hour ? resolutionValues.Hour : 0}H${ + resolutionValues.Minute ? resolutionValues.Minute : 0 + }M${resolutionValues.Second ? resolutionValues.Second : 0}S`; + + // frontend always provides temporalResolution even if there is no value resulting in P0Y0M0DT0H0M0S + // don't save if value is equal to P0Y0M0DT0H0M0S + if (valueString !== "P0Y0M0DT0H0M0S") { + RDFdataset.addQuad( + N3.DataFactory.quad( + mainURI, + N3.DataFactory.namedNode( + generalHelper.addNamespace(key, dpiConfig) + ), + N3.DataFactory.literal( + valueString, + N3.DataFactory.namedNode( + generalHelper.addNamespace("xsd:duration", dpiConfig) + ) + ) + ) + ); + } + } else if (key === "dct:identifier") { + // form provides data as array of objects with strings: [ { '@value': 'string1' }, { '@value': 'string2' }, ... ] + // create quadruple for each given object in the array + for (let valueId = 0; valueId < data[key].length; valueId += 1) { + const currentValue = data[key][valueId]; + if (has(currentValue, "@value") && !isEmpty(currentValue["@value"])) { + RDFdataset.addQuad( + N3.DataFactory.quad( + mainURI, + N3.DataFactory.namedNode( + generalHelper.addNamespace(key, dpiConfig) + ), + N3.DataFactory.literal(currentValue["@value"]) + ) + ); + } + } + } else if (key === "dct:rights") { + // rights has a static type (RightsStatement) which needs to be added to linked data as additional node + // therefore we need to create an initial quadruple for with 'rights' being the predicate having a blank node + // blank node serves as subject for the following quadruples which contain the type and actual value of the form field + // RDF: + // datasetID dct:rights blankNodeID + // blankNodeId rdf:type RightsStatement + // blankNodeId rdfs:label LabelValue + + // blank node as object for inital quadruple and also as subject for following quadruples + const rightsBlankNode = N3.DataFactory.blankNode(""); + + RDFdataset.addQuad( + N3.DataFactory.quad( + mainURI, + N3.DataFactory.namedNode( + generalHelper.addNamespace(key, dpiConfig) + ), + rightsBlankNode + ) + ); + + // add additional type declaration + RDFdataset.addQuad( + N3.DataFactory.quad( + rightsBlankNode, + N3.DataFactory.namedNode( + generalHelper.addNamespace("rdf:type", dpiConfig) + ), + N3.DataFactory.namedNode( + generalHelper.addNamespace("dct:RightsStatement", dpiConfig) + ) + ) + ); + + // rights is a conditional property and provides either an URI or a string ( { rdfs:label : 'URL/string' } ) + let rightsValue; + + if (data[key]["@type"] === "url") { + rightsValue = N3.DataFactory.namedNode(data[key]["rdfs:label"]); + } else { + rightsValue = N3.DataFactory.literal(data[key]["rdfs:label"]); + } + + // add actual value + RDFdataset.addQuad( + N3.DataFactory.quad( + rightsBlankNode, + N3.DataFactory.namedNode( + generalHelper.addNamespace("rdfs:label", dpiConfig) + ), + rightsValue + ) + ); + } } + } } /** @@ -374,38 +536,57 @@ function convertPropertyValues(RDFdataset, data, property, preMainURI, preMainTy * @param {String} property String determining which property is converted (datasets/distributions/catalogues) * @param {String} datasetURI URI of dataset used to add distribution URI to dct:distribution within dataset graph */ -function setAdditionalProperties(RDFdataset, data, mainURI, mainType, property, datasetURI, dpiConfig) { - - // adding id and type of property - RDFdataset.addQuad(N3.DataFactory.quad( +function setAdditionalProperties( + RDFdataset, + data, + mainURI, + mainType, + property, + datasetURI, + dpiConfig +) { + // adding id and type of property + RDFdataset.addQuad( + N3.DataFactory.quad( + mainURI, + N3.DataFactory.namedNode( + generalHelper.addNamespace("rdf:type", dpiConfig) + ), + N3.DataFactory.namedNode(mainType) + ) + ); + + // catalogues always have to contain the property dct:type with the value 'dcat-ap' + if (property === "catalogues") { + RDFdataset.addQuad( + N3.DataFactory.quad( mainURI, - N3.DataFactory.namedNode(generalHelper.addNamespace('rdf:type', dpiConfig)), - N3.DataFactory.namedNode(mainType) - )) - - // catalogues always have to contain the property dct:type with the value 'dcat-ap' - if (property === 'catalogues') { - RDFdataset.addQuad(N3.DataFactory.quad( - mainURI, - N3.DataFactory.namedNode(generalHelper.addNamespace('dct:type', dpiConfig)), - N3.DataFactory.literal('dcat-ap') - )) - } - - // add distribution id to dataset graph (dcat:distribution) - if (property === 'distributions') { - RDFdataset.addQuad(N3.DataFactory.quad( - N3.DataFactory.namedNode(datasetURI), - N3.DataFactory.namedNode(generalHelper.addNamespace('dcat:distribution', dpiConfig)), - mainURI - )) - } + N3.DataFactory.namedNode( + generalHelper.addNamespace("dct:type", dpiConfig) + ), + N3.DataFactory.literal("dcat-ap") + ) + ); + } + + // add distribution id to dataset graph (dcat:distribution) + if (property === "distributions") { + RDFdataset.addQuad( + N3.DataFactory.quad( + N3.DataFactory.namedNode(datasetURI), + N3.DataFactory.namedNode( + generalHelper.addNamespace("dcat:distribution", dpiConfig) + ), + mainURI + ) + ); + } } //----------------------------------------------------------------------------------------------------- // basic conversion (input to RDF) methods for different categories of data //----------------------------------------------------------------------------------------------------- -// seems unnecessary at first but if we want to convert nested properties as well, we need these +// seems unnecessary at first but if we want to convert nested properties as well, we need these // methods (especially to provide the correct parent URI) /** @@ -416,13 +597,15 @@ function setAdditionalProperties(RDFdataset, data, mainURI, mainType, property, * @param {String} key Name of current value (e.g. dct:title) used as predicate in quad */ function convertSingularString(RDFdataset, id, data, key, dpiConfig) { - if (!isEmpty(data[key])) { - RDFdataset.addQuad(N3.DataFactory.quad( - id, - N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), - N3.DataFactory.literal(data[key]) - )) - } + if (!isEmpty(data[key])) { + RDFdataset.addQuad( + N3.DataFactory.quad( + id, + N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), + N3.DataFactory.literal(data[key]) + ) + ); + } } /** @@ -433,36 +616,37 @@ function convertSingularString(RDFdataset, id, data, key, dpiConfig) { * @param {String} key Name of current value (e.g. dct:title) used as predicate in quad */ function convertSingularURI(RDFdataset, id, data, key, dpiConfig) { - // there are two different formats the frontend delivers URIs - // 1: 'URI' or 2: {'name': 'abc', 'resource': 'URI'} - - // URIs can either be a normal URL or an email address - // mail addresses typicall include '@' which is used to determine if the given string is a normal URL or an email address - if (!isEmpty(data[key])) { - - let singleURI; - - if (typeof data[key] === 'object') { - if (has(data[key], 'resource')) { - singleURI = data[key].resource; - } - } else { - if (data[key].includes('@')) { - // mail address - singleURI = `mailto:${data[key]}`; - } else { - // normal URL - singleURI = data[key]; - } - } - - // save quad to dataset - RDFdataset.addQuad(N3.DataFactory.quad( - id, - N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), - N3.DataFactory.namedNode(singleURI) - )); + // there are two different formats the frontend delivers URIs + // 1: 'URI' or 2: {'name': 'abc', 'resource': 'URI'} + + // URIs can either be a normal URL or an email address + // mail addresses typicall include '@' which is used to determine if the given string is a normal URL or an email address + if (!isEmpty(data[key])) { + let singleURI; + + if (typeof data[key] === "object") { + if (has(data[key], "resource")) { + singleURI = data[key].resource; + } + } else { + if (data[key].includes("@")) { + // mail address + singleURI = `mailto:${data[key]}`; + } else { + // normal URL + singleURI = data[key]; + } } + + // save quad to dataset + RDFdataset.addQuad( + N3.DataFactory.quad( + id, + N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), + N3.DataFactory.namedNode(singleURI) + ) + ); + } } /** @@ -474,26 +658,27 @@ function convertSingularURI(RDFdataset, id, data, key, dpiConfig) { * @param {String} property Determining which property is concerted (datasets/distributions/catalogues) */ function convertMultipleURI(RDFdataset, id, data, key, property, dpiConfig) { - // there are two different formats the frontend delivers multiple URIs - // 1: [ {"name": '...', "resource": 'URI'}, {...} ] -> multi-autocomplete fields - // 2: [ { "@id": "URI1" }, { "@id": "URI2" } ] repeatable fields - - for (let uriIndex = 0; uriIndex < data[key].length; uriIndex += 1) { - - let currentURI; - const valueObject = data[key][uriIndex]; - if (!isEmpty(valueObject)) { - if (has(valueObject, 'resource')) currentURI = valueObject.resource; - else if (has(valueObject, '@id')) currentURI = valueObject['@id']; - } - - // save quad to dataset - RDFdataset.addQuad(N3.DataFactory.quad( - id, - N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), - N3.DataFactory.namedNode(currentURI) - )); + // there are two different formats the frontend delivers multiple URIs + // 1: [ {"name": '...', "resource": 'URI'}, {...} ] -> multi-autocomplete fields + // 2: [ { "@id": "URI1" }, { "@id": "URI2" } ] repeatable fields + + for (let uriIndex = 0; uriIndex < data[key].length; uriIndex += 1) { + let currentURI; + const valueObject = data[key][uriIndex]; + if (!isEmpty(valueObject)) { + if (has(valueObject, "resource")) currentURI = valueObject.resource; + else if (has(valueObject, "@id")) currentURI = valueObject["@id"]; } + + // save quad to dataset + RDFdataset.addQuad( + N3.DataFactory.quad( + id, + N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), + N3.DataFactory.namedNode(currentURI) + ) + ); + } } /** @@ -504,41 +689,63 @@ function convertMultipleURI(RDFdataset, id, data, key, property, dpiConfig) { * @param {String} key Name of current value (e.g. dct:title) used as predicate in quad and to determine quad-object type */ function convertTypedString(RDFdataset, id, data, key, dpiConfig) { - if (!isEmpty(data[key])) { - - // there is a variety of properties which can have different types - // issued and motified already provide a type definition ({'@type': 'date/datetime', '@value': '...'}) - if (key === 'dct:issued' || key === 'dct:modified') { - if (has(data[key], '@value') && !isEmpty(data[key]['@value'])) { - const imValueType = data[key]['@type'] === 'date' ? data[key]['@type'] : 'dateTime'; - const valueType = generalHelper.addNamespace(`xsd:${imValueType}`, dpiConfig); - - /// save quad to dataset - RDFdataset.addQuad(N3.DataFactory.quad( - id, - N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), - N3.DataFactory.literal(data[key]['@value'], N3.DataFactory.namedNode(valueType)) - )); - } - } else { - // all other properties are given as a simple string - let valueType; - if (key === 'dcat:endDate' || key === 'dcat:startDate') { - // dcat:endDate and dcat:startDate are xsd:dateTime - valueType = generalHelper.addNamespace('xsd:dateTime', dpiConfig); - } else if (key === 'dcat:spatialResolutionInMeters' || key === "dcat:byteSize") { - // dcat:spatialResolutionInMeters and dcat:byteSize are xsd:decimal - valueType = generalHelper.addNamespace('xsd:decimal', dpiConfig); - } - - /// save quad to dataset - RDFdataset.addQuad(N3.DataFactory.quad( - id, - N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), - N3.DataFactory.literal(data[key], N3.DataFactory.namedNode(valueType)) - )); - } + if (!isEmpty(data[key])) { + // there is a variety of properties which can have different types + // issued and motified already provide a type definition ({'@type': 'date/datetime', '@value': '...'}) + if (key === "dct:issued" || key === "dct:modified") { + if (has(data[key], "@value") && !isEmpty(data[key]["@value"])) { + const imValueType = + data[key]["@type"] === "date" ? data[key]["@type"] : "dateTime"; + const valueType = generalHelper.addNamespace( + `xsd:${imValueType}`, + dpiConfig + ); + + /// save quad to dataset + RDFdataset.addQuad( + N3.DataFactory.quad( + id, + N3.DataFactory.namedNode( + generalHelper.addNamespace(key, dpiConfig) + ), + N3.DataFactory.literal( + data[key]["@value"], + N3.DataFactory.namedNode(valueType) + ) + ) + ); + } + } else { + // all other properties are given as a simple string + let valueType; + if (key === "dcat:endDate" || key === "dcat:startDate") { + // dcat:endDate and dcat:startDate are xsd:dateTime + valueType = generalHelper.addNamespace("xsd:dateTime", dpiConfig); + } else if ( + key === "dcat:spatialResolutionInMeters" || + key === "dcat:byteSize" + ) { + // dcat:spatialResolutionInMeters and dcat:byteSize are xsd:decimal + valueType = generalHelper.addNamespace("xsd:decimal", dpiConfig); + } + else if ( + key === "pv:DistributionType" + + ) { + valueType = generalHelper.addNamespace("xsd:string", dpiConfig); + } + + /// save quad to dataset + RDFdataset.addQuad( + N3.DataFactory.quad( + id, + N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), + N3.DataFactory.literal(data[key], N3.DataFactory.namedNode(valueType)) + ) + ); + } + } } /** @@ -549,46 +756,58 @@ function convertTypedString(RDFdataset, id, data, key, dpiConfig) { * @param {String} key Name of current value (e.g. dct:title) used as predicate in quads */ function convertMultilingual(RDFdataset, id, data, key, dpiConfig) { - // multilingual fields mostly provide data as followed - // [ { '@value': '....', '@language': '...' }, ... ] - // only the licence title provides no language - - if (!isEmpty(data[key])) { - - // licence title - if (!Array.isArray(data[key])) { - RDFdataset.addQuad(N3.DataFactory.quad( - id, - N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), - N3.DataFactory.literal(data[key]) - )) - } else { - for (let langIndex = 0; langIndex < data[key].length; langIndex += 1) { - const currentData = data[key][langIndex]; - // only save data if a value is given (forntend provides preselected language which don't need to be saved if there is no actaul value) - if (!isEmpty(currentData) && has(currentData, '@value') && !isEmpty(currentData['@value'])) { - let languageTag; - - // if there is no langauge given, set language to english - if (!has(currentData, '@language') || isEmpty(currentData, '@language')) { - languageTag = 'en'; - } else { - // if language is given, use given tag - languageTag = currentData['@language']; - } - - // saving quad to dataset - RDFdataset.addQuad(N3.DataFactory.quad( - id, - N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), - N3.DataFactory.literal(currentData['@value'], languageTag) - )) - } - } + // multilingual fields mostly provide data as followed + // [ { '@value': '....', '@language': '...' }, ... ] + // only the licence title provides no language + + if (!isEmpty(data[key])) { + // licence title + if (!Array.isArray(data[key])) { + RDFdataset.addQuad( + N3.DataFactory.quad( + id, + N3.DataFactory.namedNode(generalHelper.addNamespace(key, dpiConfig)), + N3.DataFactory.literal(data[key]) + ) + ); + } else { + for (let langIndex = 0; langIndex < data[key].length; langIndex += 1) { + const currentData = data[key][langIndex]; + // only save data if a value is given (forntend provides preselected language which don't need to be saved if there is no actaul value) + if ( + !isEmpty(currentData) && + has(currentData, "@value") && + !isEmpty(currentData["@value"]) + ) { + let languageTag; + + // if there is no langauge given, set language to english + if ( + !has(currentData, "@language") || + isEmpty(currentData, "@language") + ) { + languageTag = "en"; + } else { + // if language is given, use given tag + languageTag = currentData["@language"]; + } + + // saving quad to dataset + RDFdataset.addQuad( + N3.DataFactory.quad( + id, + N3.DataFactory.namedNode( + generalHelper.addNamespace(key, dpiConfig) + ), + N3.DataFactory.literal(currentData["@value"], languageTag) + ) + ); } + } } + } } export default { - convertToRDF, -}; \ No newline at end of file + convertToRDF, +}; diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/general-helper.js b/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/general-helper.js index 25e397085..7d0ff569d 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/general-helper.js +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/general-helper.js @@ -1,19 +1,18 @@ -import { isEmpty, isNil, has, cloneDeep } from 'lodash'; -import axios from 'axios'; +import { isEmpty, isNil, has, cloneDeep } from "lodash"; +import axios from "axios"; import { getTranslationFor } from "../../utils/helpers"; - /** * Merges multiple Objects nested within an object into one main objects with al key-value-pairs originally located within the nested objects * @param {Object} data Object containing nested objects * @returns Object with key-value pairs merged from nested objects */ function mergeNestedObjects(data) { - let mergedObject = {}; - for (const key in data) { - mergedObject = Object.assign(mergedObject, data[key]); - } - return mergedObject; + let mergedObject = {}; + for (const key in data) { + mergedObject = Object.assign(mergedObject, data[key]); + } + return mergedObject; } /** @@ -22,27 +21,33 @@ function mergeNestedObjects(data) { * @returns */ function addNamespace(prefix, dpiConfig) { - // the prefix had the following format: namespace:property (e.g. dct:title) - // the short version of the namespace noe should be replaced by the long version (e.g. http://purl.org/dc/terms/title) - let fullDescriptor; - const colonIndex = prefix.indexOf(':'); + // the prefix had the following format: namespace:property (e.g. dct:title) + // the short version of the namespace noe should be replaced by the long version (e.g. http://purl.org/dc/terms/title) + let fullDescriptor; + + // console.log(prefix); +// if (prefix === "pv:DistributionType") { +// return "pv:DistributionType"; +// } else { + const colonIndex = prefix.indexOf(":"); // there are also prefixes with no namespace which should sty the same if (colonIndex !== -1) { - const namespaceAbbreviation = prefix.substr(0, colonIndex); - const propertyName = prefix.substr(colonIndex + 1); + const namespaceAbbreviation = prefix.substr(0, colonIndex); + const propertyName = prefix.substr(colonIndex + 1); - // the long version of the namespace is saved within the context.json (config) - // there is an object containing the namespace abbreviation(key) and the corresponding value is the long version of the namespace + // the long version of the namespace is saved within the context.json (config) + // there is an object containing the namespace abbreviation(key) and the corresponding value is the long version of the namespace - const longNamespace = dpiConfig.prefixes[namespaceAbbreviation]; - fullDescriptor = `${longNamespace}${propertyName}`; + const longNamespace = dpiConfig.prefixes[namespaceAbbreviation]; + fullDescriptor = `${longNamespace}${propertyName}`; } else { - fullDescriptor = prefix; + fullDescriptor = prefix; } return fullDescriptor; -} + } +// } /** * Removes long namespace and replaces it with the abbreviation of the namespace @@ -50,20 +55,22 @@ function addNamespace(prefix, dpiConfig) { * @returns Returns value with short namespace (e.g. rdf:type) */ function removeNamespace(longValue, dpiConfig) { - let lastIndex; - - // long namespace either ends with an # or a \ - if (longValue.includes('#')) { - lastIndex = longValue.lastIndexOf('#') - } else { - lastIndex = longValue.lastIndexOf('/') - } - - const shortValue = longValue.substr(lastIndex + 1); - const longPrefix = longValue.substr(0, lastIndex + 1); - const shortPrefix = Object.keys(dpiConfig.prefixes).find(key => dpiConfig.prefixes[key] === longPrefix); - - return `${shortPrefix}:${shortValue}`; + let lastIndex; + + // long namespace either ends with an # or a \ + if (longValue.includes("#")) { + lastIndex = longValue.lastIndexOf("#"); + } else { + lastIndex = longValue.lastIndexOf("/"); + } + + const shortValue = longValue.substr(lastIndex + 1); + const longPrefix = longValue.substr(0, lastIndex + 1); + const shortPrefix = Object.keys(dpiConfig.prefixes).find( + (key) => dpiConfig.prefixes[key] === longPrefix + ); + + return `${shortPrefix}:${shortValue}`; } /** @@ -72,13 +79,13 @@ function removeNamespace(longValue, dpiConfig) { * @returns Array of shortened keys */ function getNestedKeys(data, dpiConfig) { - const keys = []; + const keys = []; - for (let el of data) { - keys.push(removeNamespace(el.predicate.value, dpiConfig)); - } + for (let el of data) { + keys.push(removeNamespace(el.predicate.value, dpiConfig)); + } - return keys; + return keys; } /** @@ -87,13 +94,14 @@ function getNestedKeys(data, dpiConfig) { * @returns String formed of random characters with given length */ function makeId(length) { - var result = ''; - var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - var charactersLength = characters.length; - for (var i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); - } - return result; + var result = ""; + var characters = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + var charactersLength = characters.length; + for (var i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; } /** @@ -102,13 +110,13 @@ function makeId(length) { * @returns Boolean determining if given string is an Url */ function isUrl(string) { - let url; - try { - url = new URL(string); - } catch (_) { - return false; - } - return url.protocol === "http:" || url.protocol === "https:"; + let url; + try { + url = new URL(string); + } catch (_) { + return false; + } + return url.protocol === "http:" || url.protocol === "https:"; } /** @@ -118,58 +126,61 @@ function isUrl(string) { * @returns Returns promise of fetched data */ async function fetchLinkedData(endpoint, token) { - let response; - let requestOptions; - - // if token is given, provide token (for drafts and other non-public elements) - if (token !== '') { - requestOptions = { - method: 'GET', - headers: { - Authorization: `Bearer ${token}`, - }, - url: endpoint, - }; - } else { - requestOptions = { - method: 'GET', - url: endpoint, - }; - } + let response; + let requestOptions; + + // if token is given, provide token (for drafts and other non-public elements) + if (token !== "") { + requestOptions = { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + }, + url: endpoint, + }; + } else { + requestOptions = { + method: "GET", + url: endpoint, + }; + } + + try { + response = fetch(endpoint, requestOptions) + .then((response) => { + const reader = response?.body?.getReader(); + return new ReadableStream({ + start(controller) { + // The following function handles each data chunk + function push() { + // "done" is a Boolean and value a "Uint8Array" + reader?.read().then(({ done, value }) => { + // If there is no more data to read + if (done) { + controller.close(); + return; + } + // Get the data and send it to the browser via the controller + controller.enqueue(value); + // Check chunks by logging to the console + push(); + }); + } - try { - response = fetch(endpoint, requestOptions) - .then(response => { - const reader = response?.body?.getReader(); - return new ReadableStream({ - start(controller) { - // The following function handles each data chunk - function push() { - // "done" is a Boolean and value a "Uint8Array" - reader?.read().then(({ done, value }) => { - // If there is no more data to read - if (done) { - controller.close(); - return; - } - // Get the data and send it to the browser via the controller - controller.enqueue(value); - // Check chunks by logging to the console - push(); - }); - } - - push(); - }, - }); - }).then((stream) => - new Response(stream, { headers: { 'Content-Type': 'text/html' } }).text() - ); - } catch (err) { - // TODO: Handle (network) errors - throw Error(`Error occured during fetching endpoint: ${endpoint}`); - } - return response; + push(); + }, + }); + }) + .then((stream) => + new Response(stream, { + headers: { "Content-Type": "text/html" }, + }).text() + ); + } catch (err) { + // TODO: Handle (network) errors + throw Error(`Error occured during fetching endpoint: ${endpoint}`); + } + return response; } /** @@ -180,29 +191,33 @@ async function fetchLinkedData(endpoint, token) { * @returns Object containing keys of properties for each page */ function getPagePrefixedNames(property, formDefinitions, pageContent) { - - const prefixedNames = { - datasets: {}, - distributions: {}, - catalogues: {} - }; - - // get property keys for each page - for (let pageName in pageContent[property]) { - prefixedNames[property][pageName] = []; - const propertyKeys = pageContent[property][pageName]; - for (let propertyindex = 0; propertyindex < propertyKeys.length; propertyindex++) { - const propertyName = propertyKeys[propertyindex]; - const prefixedName = formDefinitions[property][propertyName].name; // form definition includes name-property which contains key - if (prefixedName !== undefined) prefixedNames[property][pageName].push(prefixedName); - } + const prefixedNames = { + datasets: {}, + distributions: {}, + catalogues: {}, + }; + + // get property keys for each page + for (let pageName in pageContent[property]) { + prefixedNames[property][pageName] = []; + const propertyKeys = pageContent[property][pageName]; + for ( + let propertyindex = 0; + propertyindex < propertyKeys.length; + propertyindex++ + ) { + const propertyName = propertyKeys[propertyindex]; + const prefixedName = formDefinitions[property][propertyName].name; // form definition includes name-property which contains key + if (prefixedName !== undefined) + prefixedNames[property][pageName].push(prefixedName); } + } - return prefixedNames; + return prefixedNames; } function isValid(id) { - return /^[a-z0-9-]*$/.test(id); + return /^[a-z0-9-]*$/.test(id); } /** @@ -213,183 +228,187 @@ function isValid(id) { * @returns {string|null} */ function getFileIdByAccessUrl({ accessUrl, fileUploadUrl }) { - const accessUrlWithTrailingSlash = accessUrl.endsWith('/') - ? accessUrl - : `${accessUrl}/`; - const fileUploadUrlWithTrailingSlash = fileUploadUrl.endsWith('/') - ? fileUploadUrl - : `${fileUploadUrl}/`; - - // Check if accessUrl starts with fileUploadApi - if (accessUrlWithTrailingSlash.startsWith(fileUploadUrlWithTrailingSlash)) { - const accessUrlParts = accessUrlWithTrailingSlash.split('/'); - const fileId = accessUrlParts[accessUrlParts.length - 2]; - - return fileId || null; - } - - return null; + const accessUrlWithTrailingSlash = accessUrl.endsWith("/") + ? accessUrl + : `${accessUrl}/`; + const fileUploadUrlWithTrailingSlash = fileUploadUrl.endsWith("/") + ? fileUploadUrl + : `${fileUploadUrl}/`; + + // Check if accessUrl starts with fileUploadApi + if (accessUrlWithTrailingSlash.startsWith(fileUploadUrlWithTrailingSlash)) { + const accessUrlParts = accessUrlWithTrailingSlash.split("/"); + const fileId = accessUrlParts[accessUrlParts.length - 2]; + + return fileId || null; + } + + return null; } /** - * Adds given key to format type - * @param {String} key - * @param {String} format - * @param {String} property - * @param {Object} typeDefinition + * Adds given key to format type + * @param {String} key + * @param {String} format + * @param {String} property + * @param {Object} typeDefinition */ function addKeyToFormatType(key, format, property, typeDefinition) { - typeDefinition[format][property].push(key); + typeDefinition[format][property].push(key); } /** * Removes key from format type - * @param {String} key - * @param {String} format - * @param {String} property - * @param {Object} typeDefinition + * @param {String} key + * @param {String} format + * @param {String} property + * @param {Object} typeDefinition */ function removeKeyFromFormatType(key, format, property, typeDefinition) { - typeDefinition[format][property].splice(typeDefinition[format][property].indexOf(key), 1); + typeDefinition[format][property].splice( + typeDefinition[format][property].indexOf(key), + 1 + ); } function propertyObjectHasValues(objectData) { - let objectHasValues = false; - if (!isNil(objectData) && !isEmpty(objectData)) { - // language tag is always given - let copiedData = cloneDeep(objectData) + let objectHasValues = false; + if (!isNil(objectData) && !isEmpty(objectData)) { + // language tag is always given + let copiedData = cloneDeep(objectData); - if (has(copiedData, '@language')) { - delete copiedData['@language']; - } + if (has(copiedData, "@language")) { + delete copiedData["@language"]; + } - // removing all falsy values (undefined, null, "", '', NaN, 0) - const actualValues = Object.values(copiedData).filter(el => el); // filters all real values - if (!isEmpty(actualValues)) { - // there are keys containing an object or array as value - for (let valueIndex = 0; valueIndex < actualValues.length; valueIndex++) { - // if at least one elemnt within the array is set, return true - const currentValue = actualValues[valueIndex]; - - // testing content of array - if (Array.isArray(currentValue)) { - // there are only objects wihtin those arrays - for (let arrIndex = 0; arrIndex < currentValue.length; arrIndex++) { - if (propertyObjectHasValues(currentValue[arrIndex])) objectHasValues = true; - } - } else if (typeof currentValue === 'object') { // testing content of object - if (propertyObjectHasValues(currentValue)) objectHasValues = true; - } else { - objectHasValues = true; - } - } + // removing all falsy values (undefined, null, "", '', NaN, 0) + const actualValues = Object.values(copiedData).filter((el) => el); // filters all real values + if (!isEmpty(actualValues)) { + // there are keys containing an object or array as value + for (let valueIndex = 0; valueIndex < actualValues.length; valueIndex++) { + // if at least one elemnt within the array is set, return true + const currentValue = actualValues[valueIndex]; + + // testing content of array + if (Array.isArray(currentValue)) { + // there are only objects wihtin those arrays + for (let arrIndex = 0; arrIndex < currentValue.length; arrIndex++) { + if (propertyObjectHasValues(currentValue[arrIndex])) + objectHasValues = true; + } + } else if (typeof currentValue === "object") { + // testing content of object + if (propertyObjectHasValues(currentValue)) objectHasValues = true; + } else { + objectHasValues = true; } + } } + } - return objectHasValues; + return objectHasValues; } function propertyHasValue(data) { - - let isSet = false; - - if (data !== undefined && data !== "" && !isEmpty(data) && !isNil(data)) { - // testing array data - if (Array.isArray(data)) { - // there are arreay of objects or arrays of values - if (data.every(el => typeof el === 'string')) { - isSet = !isEmpty(data.filter(el => el)); - } else if (data.every(el => typeof el === 'object')) { - for (let index = 0; index < data.length; index++) { - // if at least one array element is set, return true - if (propertyObjectHasValues(data[index])) isSet = true; - } - } - } else if (typeof data === 'object') { - // testing object data - isSet = propertyObjectHasValues(data); - } else { - isSet = true; + let isSet = false; + + if (data !== undefined && data !== "" && !isEmpty(data) && !isNil(data)) { + // testing array data + if (Array.isArray(data)) { + // there are arreay of objects or arrays of values + if (data.every((el) => typeof el === "string")) { + isSet = !isEmpty(data.filter((el) => el)); + } else if (data.every((el) => typeof el === "object")) { + for (let index = 0; index < data.length; index++) { + // if at least one array element is set, return true + if (propertyObjectHasValues(data[index])) isSet = true; } + } + } else if (typeof data === "object") { + // testing object data + isSet = propertyObjectHasValues(data); + } else { + isSet = true; } + } - return isSet; + return isSet; } /** - * + * */ async function requestUriLabel(uri, dpiConfig, envs) { + // get vocabulary by finding vocab-url within given URI + const voc = Object.keys(dpiConfig.vocabPrefixes).find((key) => + uri.includes(dpiConfig.vocabPrefixes[key]) + ); - // get vocabulary by finding vocab-url within given URI - const voc = Object.keys(dpiConfig.vocabPrefixes).find(key => uri.includes(dpiConfig.vocabPrefixes[key])); - - try { - let req; - - // vocabularies for spdx checksum and inana-media-types are structured differently in the backend then other vocabularies - if (voc === 'iana-media-types' || voc === 'spdx-checksum-algorithm') { - req = `${envs.api.baseUrl}vocabularies/${voc}`; - - } else { - const value = uri.replace(dpiConfig.vocabPrefixes[voc], ''); - req = `${envs.api.baseUrl}vocabularies/${voc}/${value}`; - } + try { + let req; - return new Promise((resolve, reject) => { - axios.get(req) - .then((res) => { - resolve(res); - }) - .catch((err) => { - reject(err); + // vocabularies for spdx checksum and inana-media-types are structured differently in the backend then other vocabularies + if (voc === "iana-media-types" || voc === "spdx-checksum-algorithm") { + req = `${envs.api.baseUrl}vocabularies/${voc}`; + } else { + const value = uri.replace(dpiConfig.vocabPrefixes[voc], ""); + req = `${envs.api.baseUrl}vocabularies/${voc}/${value}`; + } - }); + return new Promise((resolve, reject) => { + axios + .get(req) + .then((res) => { + resolve(res); + }) + .catch((err) => { + reject(err); }); - } catch (error) { - // - } + }); + } catch (error) { + // + } } - /** - * + * */ async function getUriLabel(uri, dpiConfig, locale, envs) { - let URIlabel; + let URIlabel; - const voc = Object.keys(dpiConfig.vocabPrefixes).find(key => uri.includes(dpiConfig.vocabPrefixes[key])); + const voc = Object.keys(dpiConfig.vocabPrefixes).find((key) => + uri.includes(dpiConfig.vocabPrefixes[key]) + ); - // if vocabulary iana media type or spdx checksum endpoint returns values in a different way - let vocMatch = (voc === "iana-media-types" || voc === "spdx-checksum-algorithm"); + // if vocabulary iana media type or spdx checksum endpoint returns values in a different way + let vocMatch = + voc === "iana-media-types" || voc === "spdx-checksum-algorithm"; - await requestUriLabel(uri, dpiConfig, envs).then( - (response) => { - let result = vocMatch - ? response.data.result.results - .filter((dataset) => dataset.resource === uri) - .map((dataset) => dataset.pref_label)[0].en - : getTranslationFor(response.data.result.pref_label, locale, []); + await requestUriLabel(uri, dpiConfig, envs).then((response) => { + let result = vocMatch + ? response.data.result.results + .filter((dataset) => dataset.resource === uri) + .map((dataset) => dataset.pref_label)[0].en + : getTranslationFor(response.data.result.pref_label, locale, []); - URIlabel = result; - } - ); + URIlabel = result; + }); - return URIlabel; + return URIlabel; } export default { - mergeNestedObjects, - addNamespace, - makeId, - isUrl, - fetchLinkedData, - getPagePrefixedNames, - getNestedKeys, - removeNamespace, - getFileIdByAccessUrl, - addKeyToFormatType, - removeKeyFromFormatType, - propertyHasValue, - getUriLabel, + mergeNestedObjects, + addNamespace, + makeId, + isUrl, + fetchLinkedData, + getPagePrefixedNames, + getNestedKeys, + removeNamespace, + getFileIdByAccessUrl, + addKeyToFormatType, + removeKeyFromFormatType, + propertyHasValue, + getUriLabel, }; diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/inputConverter.js b/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/inputConverter.js index c4005d65b..7bab57da1 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/inputConverter.js +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/inputConverter.js @@ -1,6 +1,5 @@ import generalHelper from "./general-helper"; -import { has, isEmpty } from 'lodash'; - +import { has, isEmpty } from "lodash"; /** * Converts given data for given property into input form format @@ -8,48 +7,83 @@ import { has, isEmpty } from 'lodash'; * @param {*} property Property to convert data for (datasets/catalogues) * @param {*} data Linked data within a dataset */ -async function convertToInput(state, property, data, dpiConfig ) { - - let generalID; - let namespaceKeys; - let propertyQuads; - - if (property === 'datasets') { - propertyQuads = data.match(null, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'http://www.w3.org/ns/dcat#Dataset', null); - } else { - propertyQuads = data.match(null, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'http://www.w3.org/ns/dcat#Catalog', null); - } - - // extract data for datasets/catalogues - namespaceKeys = generalHelper.getPagePrefixedNames(property, dpiConfig.inputDefinition, dpiConfig.pageConent); - state[property] = {}; - for (let el of propertyQuads) { - // there should be only one dataset id - generalID = el.subject.value; - - for (let pageName in namespaceKeys[property]) { - state[property][pageName] = {}; - convertProperties(property, state[property][pageName], generalID, data, namespaceKeys[property][pageName], dpiConfig); - } - +async function convertToInput(state, property, data, dpiConfig) { + let generalID; + let namespaceKeys; + let propertyQuads; + + if (property === "datasets") { + propertyQuads = data.match( + null, + "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", + "http://www.w3.org/ns/dcat#Dataset", + null + ); + } else { + propertyQuads = data.match( + null, + "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", + "http://www.w3.org/ns/dcat#Catalog", + null + ); + } + + // extract data for datasets/catalogues + namespaceKeys = generalHelper.getPagePrefixedNames( + property, + dpiConfig.inputDefinition, + dpiConfig.pageConent + ); + state[property] = {}; + for (let el of propertyQuads) { + // there should be only one dataset id + generalID = el.subject.value; + + for (let pageName in namespaceKeys[property]) { + state[property][pageName] = {}; + convertProperties( + property, + state[property][pageName], + generalID, + data, + namespaceKeys[property][pageName], + dpiConfig + ); } - - // also add distribution data - if (property === 'datasets') { - const distributionQuads = data.match(generalID, 'http://www.w3.org/ns/dcat#distribution', null, null); - namespaceKeys = generalHelper.getPagePrefixedNames('distributions', dpiConfig.inputDefinition, dpiConfig.pageConent); - state.datasets.Distributions['distributionList'] = []; - for (let el of distributionQuads) { - const currentDistribution = {}; - - const distributionId = el.object.value; - for (let pageName in namespaceKeys['distributions']) { - currentDistribution[pageName] = {}; - convertProperties('distributions', currentDistribution[pageName], distributionId, data, namespaceKeys['distributions'][pageName], dpiConfig); - } - state.datasets.Distributions.distributionList.push(currentDistribution); - } + } + + // also add distribution data + if (property === "datasets") { + const distributionQuads = data.match( + generalID, + "http://www.w3.org/ns/dcat#distribution", + null, + null + ); + namespaceKeys = generalHelper.getPagePrefixedNames( + "distributions", + dpiConfig.inputDefinition, + dpiConfig.pageConent + ); + state.datasets.Distributions["distributionList"] = []; + for (let el of distributionQuads) { + const currentDistribution = {}; + + const distributionId = el.object.value; + for (let pageName in namespaceKeys["distributions"]) { + currentDistribution[pageName] = {}; + convertProperties( + "distributions", + currentDistribution[pageName], + distributionId, + data, + namespaceKeys["distributions"][pageName], + dpiConfig + ); + } + state.datasets.Distributions.distributionList.push(currentDistribution); } + } } /** @@ -61,282 +95,337 @@ async function convertToInput(state, property, data, dpiConfig ) { * @param {*} propertyKeys Keys of properties to check */ function convertProperties(property, state, id, data, propertyKeys, dpiConfig) { - - const formatType = dpiConfig.formatTypes; - - for (let index = 0; index < propertyKeys.length; index += 1) { - const key = propertyKeys[index]; - let subData = data.match(id, generalHelper.addNamespace(key, dpiConfig), null, null); - - if (formatType.singularString[property].includes(key)) { - convertSingularStrings(subData, state, key); - } else if (formatType.singularURI[property].includes(key)) { - convertSingularURI(subData, state, key, dpiConfig); - } else if (formatType.multipleURI[property].includes(key)) { - convertMultipleURI(subData, state, key, property, dpiConfig); - } else if (formatType.typedStrings[property].includes(key)) { - convertTypedString(subData, state, key); - } else if (formatType.multilingualStrings[property].includes(key)) { - convertMultilingual(subData, state, key); - } else if (formatType.conditionalProperties[property].includes(key)) { - // publisher either is an URI or a group with multiple values (name, homepage, email) - if (key === 'dct:publisher' || key === 'dct:license') { - for (let el of subData) { - if (el.object.termType === 'NamedNode') { - state[key] = {name: el.object.value, resource: el.object.value }; - } else if (el.object.termType === "BlankNode") { - - // get keys for nested values without dct'title (special format) - const nestedKeys = generalHelper.getNestedKeys(data.match(el.object, null, null, null), dpiConfig).filter(el => el !== 'dct:title'); - const nestedProperties = {}; - - // convert nested values - if (key === 'dct:license') { - const licenceTitleQuad = data.match(el.object, generalHelper.addNamespace('dct:title', dpiConfig), null, null); - for (let el of licenceTitleQuad) { - nestedProperties['dct:title'] = el.object.value; - } - } - - convertProperties(property, nestedProperties, el.object, data, nestedKeys, dpiConfig); - state[key] = nestedProperties; - } - } - } - } else if (formatType.groupedProperties[property].includes(key)) { - if (subData.size > 0) { - - state[key] = []; - // there could be multiple nodes with data for a property - for (let el of subData) { - let currentState = {} - if (key === 'skos:notation') { - // skos notation behaves differently - // there should be a typed literal given which should be seperated into @value and @type - if (el.object.value) currentState['@value'] = el.object.value; - if (el.object.datatypeString) currentState['@type'] = { name: el.object.datatypeString, resource: el.object.datatypeString }; - } else { - // some properties have a named node containing data, the value of this named node also is a value form the input form (typically @id) - if (el.object.termType === 'NamedNode') currentState['@id'] = el.object.value; - // get keys of node properties - const nestedKeys = generalHelper.getNestedKeys(data.match(el.object, null, null, null), dpiConfig); - convertProperties(property, currentState, el.object, data, nestedKeys, dpiConfig); - } - // creator not an array - if (key === 'dct:creator' || key === 'vcard:hasAddress' || key === 'skos:notation' || key === 'spdx:checksum') state[key] = currentState; - else state[key].push(currentState); - } - } - } else if (key === 'dcat:temporalResolution') { - // temporal resolution is displayed as group of input forms for each property (year, month, day, ...) - // the form provides the data as following: [ { 'Year': '...', 'Month': '...', ... } ] - // the linked data format of this property looks like this: P?Y?M?DT?H?M?S - if (subData.size > 0) { - - state[key] = {}; - - const shorts = ['Y', 'M', 'D', 'H', 'M', 'S']; - const forms = { - 0: 'Year', - 1: 'Month', - 2: 'Day', - 3: 'Hour', - 4: 'Minute', - 5: 'Second', - }; - - // should be oly one quad - let resolutionValue; - for (let el of subData) { - resolutionValue = el.object.value; - } - - // backend converts temporalResolution values without a date into seconds for time values - if (!resolutionValue.startsWith("P")) { - - // setting year, month and day to 0 - state[key][forms[0]] = 0; - state[key][forms[1]] = 0; - state[key][forms[2]] = 0; - - // converting seconds into HH:MM:SS - const data = new Date(resolutionValue * 1000).toISOString().slice(11, 19); - state[key][forms[3]] = data.slice(0, 2); - state[key][forms[4]] = data.slice(3, 5); - state[key][forms[5]] = data.slice(7, 9); - - } else { - // find index of letter for time period - // extract substring until this index - // extract number from string and set as according value for input - for (let tempIndex = 0; tempIndex < shorts.length; tempIndex += 1) { - const position = resolutionValue.indexOf(shorts[tempIndex]); // position of duration letter - const subDuration = resolutionValue.substring(0, position); // substring until position of duration letter - const value = subDuration?.match(/\d+/g)?.[0] ?? ''; // extract number - resolutionValue = resolutionValue.substring(position); // overwrite resolution string with shortened version (missing the extracted part) - state[key][forms[tempIndex]] = value; // write to result object - } - } - } - } else if (key === 'dct:identifier') { - if (subData.size > 0) { - // identifier should be provided as array of strings - // [{'@value': '...'}, {'@value': '...'}, ...] - state[key] = []; - - for (let el of subData) { - state[key].push({ '@value': el.object.value }); - } - } - } else if (key === 'dct:rights') { - // rights is conditional and gets a string - // rights always includes a type so everything is located within a blank node - // also rights is a singular property - if (subData.size > 0) { - let nodeData; - // get id of blank node and associated label data - for (let el of subData) { - const rightsBlankNode = el.object; - nodeData = data.match(rightsBlankNode, generalHelper.addNamespace('rdfs:label', dpiConfig), null, null); - for (let label of nodeData) { - if (generalHelper.isUrl(label.object.value)) state[key] = { '@type': 'url', 'rdfs:value': label.object.value }; - else state[key] = { '@type': 'text', 'rdfs:value': label.object.value }; - } - } - } - } else if (key === 'datasetID' && property !== 'datatsets') { - // id is given as complete URI - // dataset-/catalogue-id is string following the last / - state[key] = id.substr(id.lastIndexOf('/') + 1); - state['hidden_datasetIDFormHidden'] = id.substr(id.lastIndexOf('/') + 1); - } else if (key === 'dcat:catalog' && property === 'datasets') { - // datasets also have a property called dcat:catalog (not valid DCAT-AP) - // property is needed to determine catalog the dataset belongs to - if (!(subData.size > 0)) { - // bceause dcat:catalog is no valid DCAT-AP it is possible that the prefix is not resolved - // therefore it is also possible to get the data by using the shortned key - subData = data.match(id, 'dcat:catalog', null, null); + const formatType = dpiConfig.formatTypes; + + for (let index = 0; index < propertyKeys.length; index += 1) { + const key = propertyKeys[index]; + let subData = data.match( + id, + generalHelper.addNamespace(key, dpiConfig), + null, + null + ); + + if (formatType.singularString[property].includes(key)) { + convertSingularStrings(subData, state, key); + } else if (formatType.singularURI[property].includes(key)) { + convertSingularURI(subData, state, key, dpiConfig); + } else if (formatType.multipleURI[property].includes(key)) { + convertMultipleURI(subData, state, key, property, dpiConfig); + } else if (formatType.typedStrings[property].includes(key)) { + convertTypedString(subData, state, key); + } else if (formatType.multilingualStrings[property].includes(key)) { + convertMultilingual(subData, state, key); + } else if (formatType.conditionalProperties[property].includes(key)) { + // publisher either is an URI or a group with multiple values (name, homepage, email) + if (key === "dct:publisher" || key === "dct:license") { + for (let el of subData) { + if (el.object.termType === "NamedNode") { + state[key] = { name: el.object.value, resource: el.object.value }; + } else if (el.object.termType === "BlankNode") { + // get keys for nested values without dct'title (special format) + const nestedKeys = generalHelper + .getNestedKeys(data.match(el.object, null, null, null), dpiConfig) + .filter((el) => el !== "dct:title"); + const nestedProperties = {}; + + // convert nested values + if (key === "dct:license") { + const licenceTitleQuad = data.match( + el.object, + generalHelper.addNamespace("dct:title", dpiConfig), + null, + null + ); + for (let el of licenceTitleQuad) { + nestedProperties["dct:title"] = el.object.value; + } } - state[key] = ''; + convertProperties( + property, + nestedProperties, + el.object, + data, + nestedKeys, + dpiConfig + ); + state[key] = nestedProperties; + } + } + } + } else if (formatType.groupedProperties[property].includes(key)) { + if (subData.size > 0) { + state[key] = []; + // there could be multiple nodes with data for a property + for (let el of subData) { + let currentState = {}; + if (key === "skos:notation") { + // skos notation behaves differently + // there should be a typed literal given which should be seperated into @value and @type + if (el.object.value) currentState["@value"] = el.object.value; + if (el.object.datatypeString) + currentState["@type"] = { + name: el.object.datatypeString, + resource: el.object.datatypeString, + }; + } else { + // some properties have a named node containing data, the value of this named node also is a value form the input form (typically @id) + if (el.object.termType === "NamedNode") + currentState["@id"] = el.object.value; + // get keys of node properties + const nestedKeys = generalHelper.getNestedKeys( + data.match(el.object, null, null, null), + dpiConfig + ); + convertProperties( + property, + currentState, + el.object, + data, + nestedKeys, + dpiConfig + ); + } + // creator not an array + if ( + key === "dct:creator" || + key === "vcard:hasAddress" || + key === "skos:notation" || + key === "spdx:checksum" + ) + state[key] = currentState; + else state[key].push(currentState); + } + } + } else if (key === "dcat:temporalResolution") { + // temporal resolution is displayed as group of input forms for each property (year, month, day, ...) + // the form provides the data as following: [ { 'Year': '...', 'Month': '...', ... } ] + // the linked data format of this property looks like this: P?Y?M?DT?H?M?S + if (subData.size > 0) { + state[key] = {}; + + const shorts = ["Y", "M", "D", "H", "M", "S"]; + const forms = { + 0: "Year", + 1: "Month", + 2: "Day", + 3: "Hour", + 4: "Minute", + 5: "Second", + }; + + // should be oly one quad + let resolutionValue; + for (let el of subData) { + resolutionValue = el.object.value; + } - // there should only be one catalog - for (let cat of subData) { - state[key] = cat.object.value; - } - } else if (key === 'rdf:type') { - // some properties have a type which can be selected - // the type also has a namespace and therefore need to be shortened ( e.g. from https://...Individual to vcard:Individual) - if (subData.size > 0) { - state[key] = ''; - - // typically there is only on type provided for each property instance - for (let el of subData) { - state[key] = generalHelper.removeNamespace(el.object.value, dpiConfig); - } - } + // backend converts temporalResolution values without a date into seconds for time values + if (!resolutionValue.startsWith("P")) { + // setting year, month and day to 0 + state[key][forms[0]] = 0; + state[key][forms[1]] = 0; + state[key][forms[2]] = 0; + + // converting seconds into HH:MM:SS + const data = new Date(resolutionValue * 1000) + .toISOString() + .slice(11, 19); + state[key][forms[3]] = data.slice(0, 2); + state[key][forms[4]] = data.slice(3, 5); + state[key][forms[5]] = data.slice(7, 9); + } else { + // find index of letter for time period + // extract substring until this index + // extract number from string and set as according value for input + for (let tempIndex = 0; tempIndex < shorts.length; tempIndex += 1) { + const position = resolutionValue.indexOf(shorts[tempIndex]); // position of duration letter + const subDuration = resolutionValue.substring(0, position); // substring until position of duration letter + const value = subDuration?.match(/\d+/g)?.[0] ?? ""; // extract number + resolutionValue = resolutionValue.substring(position); // overwrite resolution string with shortened version (missing the extracted part) + state[key][forms[tempIndex]] = value; // write to result object + } + } + } + } else if (key === "dct:identifier") { + if (subData.size > 0) { + // identifier should be provided as array of strings + // [{'@value': '...'}, {'@value': '...'}, ...] + state[key] = []; + + for (let el of subData) { + state[key].push({ "@value": el.object.value }); + } + } + } else if (key === "dct:rights") { + // rights is conditional and gets a string + // rights always includes a type so everything is located within a blank node + // also rights is a singular property + if (subData.size > 0) { + let nodeData; + // get id of blank node and associated label data + for (let el of subData) { + const rightsBlankNode = el.object; + nodeData = data.match( + rightsBlankNode, + generalHelper.addNamespace("rdfs:label", dpiConfig), + null, + null + ); + for (let label of nodeData) { + if (generalHelper.isUrl(label.object.value)) + state[key] = { "@type": "url", "rdfs:value": label.object.value }; + else + state[key] = { + "@type": "text", + "rdfs:value": label.object.value, + }; + } } + } + } else if (key === "datasetID" && property !== "datatsets") { + // id is given as complete URI + // dataset-/catalogue-id is string following the last / + state[key] = id.substr(id.lastIndexOf("/") + 1); + state["hidden_datasetIDFormHidden"] = id.substr(id.lastIndexOf("/") + 1); + } else if (key === "dcat:catalog" && property === "datasets") { + // datasets also have a property called dcat:catalog (not valid DCAT-AP) + // property is needed to determine catalog the dataset belongs to + if (!(subData.size > 0)) { + // bceause dcat:catalog is no valid DCAT-AP it is possible that the prefix is not resolved + // therefore it is also possible to get the data by using the shortned key + subData = data.match(id, "dcat:catalog", null, null); + } + + state[key] = ""; + + // there should only be one catalog + for (let cat of subData) { + state[key] = cat.object.value; + } + } else if (key === "rdf:type") { + // some properties have a type which can be selected + // the type also has a namespace and therefore need to be shortened ( e.g. from https://...Individual to vcard:Individual) + if (subData.size > 0) { + state[key] = ""; + + // typically there is only on type provided for each property instance + for (let el of subData) { + state[key] = generalHelper.removeNamespace( + el.object.value, + dpiConfig + ); + } + } + } else if (key === "pv:DistributionType") { + // Displays the Tag of the Distribution TODO + console.log(state[key]); } + } } //----------------------------------------------------------------------------------------------------- // basic conversion methods for different categories of data //----------------------------------------------------------------------------------------------------- -// seems at first to be unnecessary but if we want to convert nested properties as well, we need these +// seems at first to be unnecessary but if we want to convert nested properties as well, we need these // methods (especially to provide the correct parent URI) /** - * - * @param {*} data - * @param {*} state - * @param {*} key + * + * @param {*} data + * @param {*} state + * @param {*} key */ function convertSingularStrings(data, state, key) { - if (data.size > 0) { - state[key] = ''; + if (data.size > 0) { + state[key] = ""; - for (let el of data) { - state[key] = el.object.value; - } + for (let el of data) { + state[key] = el.object.value; } + } } /** - * - * @param {*} data - * @param {*} state - * @param {*} key + * + * @param {*} data + * @param {*} state + * @param {*} key */ function convertSingularURI(data, state, key, dpiConfig) { - - const formatType = dpiConfig.formatTypes; - - if (data.size > 0) { - state[key] = ''; - - for (let el of data) { - const value = el.object.value; - - if (value.startsWith('mailto:')) { - state[key] = value.replace('mailto:', ''); - } else { - if (formatType.URIformat.voc.includes(key)) state[key] = { name: value, resource: value }; - else if (formatType.URIformat.string.includes(key)) state[key] = value; - else state[key] = { '@id': value }; - } - } + const formatType = dpiConfig.formatTypes; + + if (data.size > 0) { + state[key] = ""; + + for (let el of data) { + const value = el.object.value; + + if (value.startsWith("mailto:")) { + state[key] = value.replace("mailto:", ""); + } else { + if (formatType.URIformat.voc.includes(key)) + state[key] = { name: value, resource: value }; + else if (formatType.URIformat.string.includes(key)) state[key] = value; + else state[key] = { "@id": value }; + } } + } } /** - * - * @param {*} data - * @param {*} state - * @param {*} key + * + * @param {*} data + * @param {*} state + * @param {*} key */ function convertMultipleURI(data, state, key, property, dpiConfig) { - // there are two different formats the frontend need to deliver multiple URIs - // 1: [ "URI1", "URI2" ] - // 2: [ { "@id": "URI1" }, { "@id": "URI2" } ] - - const formatType = dpiConfig.formatTypes; - - if (data.size > 0) { - state[key] = []; - for (let el of data) { - if (formatType.URIformat.voc.includes(key)) state[key].push({ name: el.object.value, resource: el.object.value }); - else if (formatType.URIformat.string.includes(key)) state[key].push(el.object.value); - else state[key].push({ '@id': el.object.value }); - } + // there are two different formats the frontend need to deliver multiple URIs + // 1: [ "URI1", "URI2" ] + // 2: [ { "@id": "URI1" }, { "@id": "URI2" } ] + + const formatType = dpiConfig.formatTypes; + + if (data.size > 0) { + state[key] = []; + for (let el of data) { + if (formatType.URIformat.voc.includes(key)) + state[key].push({ name: el.object.value, resource: el.object.value }); + else if (formatType.URIformat.string.includes(key)) + state[key].push(el.object.value); + else state[key].push({ "@id": el.object.value }); } + } } /** - * - * @param {*} data - * @param {*} state - * @param {*} key + * + * @param {*} data + * @param {*} state + * @param {*} key */ function convertTypedString(data, state, key) { - // some properties have a type - // normally this type is not used within the forntend form and therefore won't be saved to frontend values - if (data.size > 0) { - state[key] = ''; - for (let el of data) { - if (key === 'dcat:spatialResolutionInMeters' || key === 'dcat:byteSize') state[key] = el.object.value; - else if (key === 'dcat:startDate' || key === 'dcat:endDate') { - state[key] = el.object.value; - } - else { - let dateType; - if (el.object.value.includes('T')) dateType = 'dateTime'; - else dateType = 'date'; - - state[key] = { '@type': dateType, '@value': el.object.value }; - } - } + // some properties have a type + // normally this type is not used within the forntend form and therefore won't be saved to frontend values + if (data.size > 0) { + state[key] = ""; + for (let el of data) { + if (key === "dcat:spatialResolutionInMeters" || key === "dcat:byteSize") + state[key] = el.object.value; + else if (key === "dcat:startDate" || key === "dcat:endDate") { + state[key] = el.object.value; + } else if (key === "pv:DistributionType") { + state[key] = el.object.value; + } else { + let dateType; + if (el.object.value.includes("T")) dateType = "dateTime"; + else dateType = "date"; + + state[key] = { "@type": dateType, "@value": el.object.value }; + } } - + } } /** @@ -346,22 +435,23 @@ function convertTypedString(data, state, key) { * @param {*} key Name of current property (e.g: 'dct:title') */ function convertMultilingual(data, state, key) { - // multilingual data is always stored within an array containing object with the value and it's language - // [ {'@value': '...', '@language': '...'}, ...] - if (data.size > 0) { - state[key] = []; - - for (let el of data) { - if (!el.object.language.includes("-")) { // machine translation language tags look like this "fi-t-en-t0-mtec" and should not be included - const currentElement = {}; - currentElement['@value'] = el.object.value; // actual value - currentElement['@language'] = el.object.language; // language of value - state[key].push(currentElement); - } - } + // multilingual data is always stored within an array containing object with the value and it's language + // [ {'@value': '...', '@language': '...'}, ...] + if (data.size > 0) { + state[key] = []; + + for (let el of data) { + if (!el.object.language.includes("-")) { + // machine translation language tags look like this "fi-t-en-t0-mtec" and should not be included + const currentElement = {}; + currentElement["@value"] = el.object.value; // actual value + currentElement["@language"] = el.object.language; // language of value + state[key].push(currentElement); + } } + } } export default { - convertToInput, + convertToInput, }; diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/StringProp.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/StringProp.vue index 03bc6534f..a0554b4cf 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/StringProp.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/StringProp.vue @@ -9,8 +9,6 @@ <!-- SINGULAR STRING --> <td v-if="value.type === 'singularString'"> - - <span v-if="data[property]['@type'] != '' && property === 'dct:rights'"> {{ data[property]['rdfs:label'] }}</span> <span v-if="property != 'dct:rights'"> {{ data[property] }} <span v-if="property === 'dcat:spatialResolutionInMeters'">Meters</span> -- GitLab