diff --git a/.gitignore b/.gitignore
index d5f19d8..2cc5f6a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
+.idea
node_modules
package-lock.json
diff --git a/index.html b/index.html
index 0ae9b8b..c16f442 100644
--- a/index.html
+++ b/index.html
@@ -1,359 +1,362 @@
CodeMeta generator
Most fields are optional. Mandatory fields will be highlighted when generating Codemeta.
codemeta.json:
+
diff --git a/js/codemeta_generation.js b/js/codemeta_generation.js
index 986e9a0..fb08f0d 100644
--- a/js/codemeta_generation.js
+++ b/js/codemeta_generation.js
@@ -1,258 +1,278 @@
/**
* Copyright (C) 2019-2020 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
*/
"use strict";
+const CODEMETA_CONTEXT_URL = 'https://doi.org/10.5063/schema/codemeta-2.0';
+const CONTEXTS = {
+ [CODEMETA_CONTEXT_URL]: 'https://raw.githubusercontent.com/codemeta/codemeta/2.0/codemeta.jsonld'
+}
+
const SPDX_PREFIX = 'https://spdx.org/licenses/';
+const jsonldCustomLoader = url => {
+ const xhrDocumentLoader = jsonld.documentLoaders.xhr();
+ if (url in CONTEXTS) {
+ return xhrDocumentLoader(CONTEXTS[url]);
+ }
+ return xhrDocumentLoader(url);
+};
+
function emptyToUndefined(v) {
if (v == null || v == "")
return undefined;
else
return v;
}
function getIfSet(query) {
return emptyToUndefined(document.querySelector(query).value);
}
function setIfDefined(query, value) {
if (value !== undefined) {
document.querySelector(query).value = value;
}
}
function getLicenses() {
let selectedLicenses = Array.from(document.getElementById("selected-licenses").children);
return selectedLicenses.map(licenseDiv => SPDX_PREFIX + licenseDiv.children[0].innerText);
}
// Names of codemeta properties with a matching HTML field name
const directCodemetaFields = [
'codeRepository',
'contIntegration',
'dateCreated',
'datePublished',
'dateModified',
'downloadUrl',
'issueTracker',
'name',
'version',
'identifier',
'description',
'applicationCategory',
'releaseNotes',
'funding',
'developmentStatus',
'isPartOf',
'referencePublication'
];
const splittedCodemetaFields = [
['keywords', ','],
['programmingLanguage', ','],
['runtimePlatform', ','],
['operatingSystem', ','],
['softwareRequirements', '\n'],
['relatedLink', '\n'],
]
// Names of codemeta properties with a matching HTML field name,
// in a Person object
const directPersonCodemetaFields = [
'givenName',
'familyName',
'email',
'affiliation',
];
function generateShortOrg(fieldName) {
var affiliation = getIfSet(fieldName);
if (affiliation !== undefined) {
if (isUrl(affiliation)) {
return {
"@type": "Organization",
"@id": affiliation,
};
}
else {
return {
"@type": "Organization",
"name": affiliation,
};
}
}
else {
return undefined;
}
}
function generatePerson(idPrefix) {
var doc = {
"@type": "Person",
"@id": getIfSet(`#${idPrefix}_id`),
}
directPersonCodemetaFields.forEach(function (item, index) {
doc[item] = getIfSet(`#${idPrefix}_${item}`);
});
doc["affiliation"] = generateShortOrg(`#${idPrefix}_affiliation`)
return doc;
}
function generatePersons(prefix) {
var persons = [];
var nbPersons = getNbPersons(prefix);
for (let personId = 1; personId <= nbPersons; personId++) {
persons.push(generatePerson(`${prefix}_${personId}`));
}
return persons;
}
-function generateCodemeta() {
- var inputForm = document.querySelector('#inputForm');
- var codemetaText, errorHTML;
- if (inputForm.checkValidity()) {
- var doc = {
- "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
- "@type": "SoftwareSourceCode",
- };
-
- let licenses = getLicenses();
- if (licenses.length > 0) {
- doc["license"] = (licenses.length === 1) ? licenses[0] : licenses;
- }
+function buildDoc() {
+ var doc = {
+ "@context": CODEMETA_CONTEXT_URL,
+ "@type": "SoftwareSourceCode",
+ };
- // Generate most fields
- directCodemetaFields.forEach(function (item, index) {
- doc[item] = getIfSet('#' + item)
- });
+ let licenses = getLicenses();
+ if (licenses.length > 0) {
+ doc["license"] = (licenses.length === 1) ? licenses[0] : licenses;
+ }
- doc["funder"] = generateShortOrg('#funder', doc["affiliation"]);
+ // Generate most fields
+ directCodemetaFields.forEach(function (item, index) {
+ doc[item] = getIfSet('#' + item)
+ });
- // Generate simple fields parsed simply by splitting
- splittedCodemetaFields.forEach(function (item, index) {
- const id = item[0];
- const separator = item[1];
- const value = getIfSet('#' + id);
- if (value !== undefined) {
- doc[id] = value.split(separator).map(trimSpaces);
- }
- });
+ doc["funder"] = generateShortOrg('#funder', doc["affiliation"]);
- // Generate dynamic fields
- var authors = generatePersons('author');
- if (authors.length > 0) {
- doc["author"] = authors;
- }
- var contributors = generatePersons('contributor');
- if (contributors.length > 0) {
- doc["contributor"] = contributors;
+ // Generate simple fields parsed simply by splitting
+ splittedCodemetaFields.forEach(function (item, index) {
+ const id = item[0];
+ const separator = item[1];
+ const value = getIfSet('#' + id);
+ if (value !== undefined) {
+ doc[id] = value.split(separator).map(trimSpaces);
}
+ });
- codemetaText = JSON.stringify(doc, null, 4);
+ // Generate dynamic fields
+ var authors = generatePersons('author');
+ if (authors.length > 0) {
+ doc["author"] = authors;
+ }
+ var contributors = generatePersons('contributor');
+ if (contributors.length > 0) {
+ doc["contributor"] = contributors;
+ }
+ return doc;
+}
+
+async function generateCodemeta() {
+ var inputForm = document.querySelector('#inputForm');
+ var codemetaText, errorHTML;
+
+ if (inputForm.checkValidity()) {
+ var doc = buildDoc();
+ const expanded = await jsonld.expand(doc, {documentLoader: jsonldCustomLoader});
+ const compacted = await jsonld.compact(expanded, CODEMETA_CONTEXT_URL, {documentLoader: jsonldCustomLoader});
+ codemetaText = JSON.stringify(compacted, null, 4);
errorHTML = "";
}
else {
codemetaText = "";
errorHTML = "invalid input (see error above)";
inputForm.reportValidity();
}
document.querySelector('#codemetaText').innerText = codemetaText;
setError(errorHTML);
// Run validator on the exported value, for extra validation.
// If this finds a validation, it means there is a bug in our code (either
// generation or validation), and the generation MUST NOT generate an
// invalid codemeta file, regardless of user input.
if (codemetaText && !validateDocument(JSON.parse(codemetaText))) {
alert('Bug detected! The data you wrote is correct; but for some reason, it seems we generated an invalid codemeta.json. Please report this bug at https://github.com/codemeta/codemeta-generator/issues/new and copy-paste the generated codemeta.json file. Thanks!');
}
if (codemetaText) {
// For restoring the form state on page reload
sessionStorage.setItem('codemetaText', codemetaText);
}
}
// Imports a single field (name or @id) from an Organization.
function importShortOrg(fieldName, doc) {
if (doc !== undefined) {
// Use @id if set, else use name
setIfDefined(fieldName, doc["name"]);
setIfDefined(fieldName, doc["@id"]);
}
}
function importPersons(prefix, legend, docs) {
if (docs === undefined) {
return;
}
docs.forEach(function (doc, index) {
var personId = addPerson(prefix, legend);
setIfDefined(`#${prefix}_${personId}_id`, doc['@id']);
directPersonCodemetaFields.forEach(function (item, index) {
setIfDefined(`#${prefix}_${personId}_${item}`, doc[item]);
});
importShortOrg(`#${prefix}_${personId}_affiliation`, doc['affiliation'])
})
}
function importCodemeta() {
var inputForm = document.querySelector('#inputForm');
var doc = parseAndValidateCodemeta(false);
resetForm();
if (doc['license'] !== undefined) {
if (typeof doc['license'] === 'string') {
doc['license'] = [doc['license']];
}
doc['license'].forEach(l => {
if (l.indexOf(SPDX_PREFIX) !== 0) { return; }
let licenseId = l.substring(SPDX_PREFIX.length);
insertLicenseElement(licenseId);
});
}
directCodemetaFields.forEach(function (item, index) {
setIfDefined('#' + item, doc[item]);
});
importShortOrg('#funder', doc["funder"]);
// Import simple fields by joining on their separator
splittedCodemetaFields.forEach(function (item, index) {
const id = item[0];
const separator = item[1];
let value = doc[id];
if (value !== undefined) {
if (Array.isArray(value)) {
value = value.join(separator);
}
setIfDefined('#' + id, value);
}
});
importPersons('author', 'Author', doc['author'])
importPersons('contributor', 'Contributor', doc['contributor'])
}
function loadStateFromStorage() {
var codemetaText = sessionStorage.getItem('codemetaText')
if (codemetaText) {
document.querySelector('#codemetaText').innerText = codemetaText;
importCodemeta();
}
}