diff --git a/cypress/integration/persons.js b/cypress/integration/persons.js
index 38734e7..941d969 100644
--- a/cypress/integration/persons.js
+++ b/cypress/integration/persons.js
@@ -1,658 +1,686 @@
 /**
  * Copyright (C) 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
  */
 
 /*
  * Tests the author/contributor dynamic fieldsets
  */
 
 "use strict";
 
 describe('Zero author', function() {
     it('can be exported', function() {
         cy.get('#name').type('My Test Software');
         cy.get('#generateCodemetaV2').click();
 
         cy.get('#errorMessage').should('have.text', '');
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
         });
     });
 
     it('can be imported from no list', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "@type": "SoftwareSourceCode",
                 "name": "My Test Software",
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#author_nb').should('have.value', '0');
         cy.get('#author_0').should('not.exist');
         cy.get('#author_1').should('not.exist');
     });
 
     it('can be imported from empty list', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "@type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [],
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#author_nb').should('have.value', '0');
         cy.get('#author_0').should('not.exist');
         cy.get('#author_1').should('not.exist');
     });
 });
 
 describe('One full author', function() {
     it('can be exported', function() {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_nb').should('have.value', '0');
         cy.get('#author_0').should('not.exist');
         cy.get('#author_1').should('not.exist');
         cy.get('#author_1_givenName').should('not.exist');
 
         cy.get('#author_add').click();
         cy.get('#author_nb').should('have.value', '1');
         cy.get('#author_0').should('not.exist');
         cy.get('#author_1').should('exist');
         cy.get('#author_2').should('not.exist');
 
         cy.get('#author_1_givenName').should('have.value', '');
         cy.get('#author_1_familyName').should('have.value', '');
         cy.get('#author_1_email').should('have.value', '');
         cy.get('#author_1_id').should('have.value', '');
         cy.get('#author_1_affiliation').should('have.value', '');
 
         cy.get('#author_1_givenName').type('Jane');
         cy.get('#author_1_familyName').type('Doe');
         cy.get('#author_1_email').type('jdoe@example.org');
         cy.get('#author_1_id').type('http://example.org/~jdoe');
         cy.get('#author_1_affiliation').type('http://example.org/');
 
         cy.get('#generateCodemetaV2').click();
 
         cy.get('#errorMessage').should('have.text', '');
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "type": "Person",
                         "id": "http://example.org/~jdoe",
                         "givenName": "Jane",
                         "familyName": "Doe",
                         "email": "jdoe@example.org",
                         "affiliation": {
                             "type": "Organization",
                             "id": "http://example.org/",
                         }
                     }
                 ],
         });
     });
 
     it('can be imported', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "@type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "@type": "Person",
                         "@id": "http://example.org/~jdoe",
                         "givenName": "Jane",
                         "familyName": "Doe",
                         "email": "jdoe@example.org",
                         "affiliation": {
                             "@type": "Organization",
                             "@id": "http://example.org/",
                         }
                     }
                 ],
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#author_nb').should('have.value', '1');
         cy.get('#author_0').should('not.exist');
         cy.get('#author_1').should('exist');
         cy.get('#author_2').should('not.exist');
         cy.get('#author_1_givenName').should('have.value', 'Jane');
         cy.get('#author_1_familyName').should('have.value', 'Doe');
         cy.get('#author_1_email').should('have.value', 'jdoe@example.org');
         cy.get('#author_1_id').should('have.value', 'http://example.org/~jdoe');
         cy.get('#author_1_affiliation').should('have.value', 'http://example.org/');
     });
 });
 
 describe('Affiliation id', function() {
     it('can be exported', function() {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
         cy.get('#author_1_affiliation').type('http://example.org/');
 
         cy.get('#generateCodemetaV2').click();
 
         cy.get('#errorMessage').should('have.text', '');
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "type": "Person",
                         "givenName": "Jane",
                         "affiliation": {
                             "type": "Organization",
                             "id": "http://example.org/",
                         }
                     }
                 ],
         });
     });
 
     it('can be imported', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "@type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "@type": "Person",
                         "@id": "http://example.org/~jdoe",
                         "givenName": "Jane",
                         "familyName": "Doe",
                         "email": "jdoe@example.org",
                         "affiliation": {
                             "@type": "Organization",
                             "@id": "http://example.org/",
                         }
                     }
                 ],
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#author_nb').should('have.value', '1');
         cy.get('#author_0').should('not.exist');
         cy.get('#author_1').should('exist');
         cy.get('#author_2').should('not.exist');
         cy.get('#author_1_affiliation').should('have.value', 'http://example.org/');
     });
 });
 
 describe('Affiliation name', function() {
     it('can be exported', function() {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
         cy.get('#author_1_affiliation').type('Example Org');
 
         cy.get('#generateCodemetaV2').click();
 
         cy.get('#errorMessage').should('have.text', '');
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "type": "Person",
                         "givenName": "Jane",
                         "affiliation": {
                             "type": "Organization",
                             "name": "Example Org",
                         }
                     }
                 ],
         });
     });
 
     it('can be imported', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "@type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "@type": "Person",
                         "@id": "http://example.org/~jdoe",
                         "givenName": "Jane",
                         "familyName": "Doe",
                         "email": "jdoe@example.org",
                         "affiliation": {
                             "@type": "Organization",
                             "name": "Example Org",
                         }
                     }
                 ],
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#author_nb').should('have.value', '1');
         cy.get('#author_0').should('not.exist');
         cy.get('#author_1').should('exist');
         cy.get('#author_2').should('not.exist');
         cy.get('#author_1_affiliation').should('have.value', 'Example Org');
     });
 });
 
 describe('Author order change', function() {
     it('is a noop with a single author', function() {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
         cy.get('#author_1_affiliation').type('Example Org');
 
         cy.get('#author_1_moveToRight').click();
 
         cy.get('#author_1_givenName').should('have.value', 'Jane');
         cy.get('#author_1_affiliation').should('have.value', 'Example Org');
 
         cy.get('#author_1_moveToLeft').click();
 
         cy.get('#author_1_givenName').should('have.value', 'Jane');
         cy.get('#author_1_affiliation').should('have.value', 'Example Org');
     });
 
     it('flips two authors', function() {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_add').click();
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
         cy.get('#author_1_affiliation').type('Example Org');
         cy.get('#author_2_givenName').type('John');
         cy.get('#author_2_familyName').type('Doe');
         cy.get('#author_3_givenName').type('Alex');
 
         cy.get('#author_1_moveToRight').click();
 
         cy.get('#author_1_givenName').should('have.value', 'John');
         cy.get('#author_1_familyName').should('have.value', 'Doe');
         cy.get('#author_1_affiliation').should('have.value', '');
 
         cy.get('#author_2_givenName').should('have.value', 'Jane');
         cy.get('#author_2_familyName').should('have.value', '');
         cy.get('#author_2_affiliation').should('have.value', 'Example Org');
 
         cy.get('#author_3_givenName').should('have.value', 'Alex');
         cy.get('#author_3_familyName').should('have.value', '');
         cy.get('#author_3_affiliation').should('have.value', '');
     });
 
     it('updates generated Codemeta', function() {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
         cy.get('#author_1_affiliation').type('Example Org');
         cy.get('#author_2_givenName').type('John');
         cy.get('#author_2_familyName').type('Doe');
 
         cy.get('#generateCodemetaV2').click();
 
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "type": "Person",
                         "givenName": "Jane",
                         "affiliation": {
                             "type": "Organization",
                             "name": "Example Org",
                         }
                     },
                     {
                         "type": "Person",
                         "givenName": "John",
                         "familyName": "Doe",
                     },
                 ],
         });
 
         cy.get('#author_1_moveToRight').click();
 
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "type": "Person",
                         "givenName": "John",
                         "familyName": "Doe",
                     },
                     {
                         "type": "Person",
                         "givenName": "Jane",
                         "affiliation": {
                             "type": "Organization",
                             "name": "Example Org",
                         }
                     },
                 ],
         });
     });
 
     it('wraps around to the right', function() {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_add').click();
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
         cy.get('#author_1_affiliation').type('Example Org');
         cy.get('#author_2_givenName').type('John');
         cy.get('#author_2_familyName').type('Doe');
         cy.get('#author_3_givenName').type('Alex');
 
         cy.get('#author_1_moveToLeft').click()
 
         cy.get('#author_1_givenName').should('have.value', 'Alex');
         cy.get('#author_1_familyName').should('have.value', '');
         cy.get('#author_1_affiliation').should('have.value', '');
 
         cy.get('#author_2_givenName').should('have.value', 'John');
         cy.get('#author_2_familyName').should('have.value', 'Doe');
         cy.get('#author_2_affiliation').should('have.value', '');
 
         cy.get('#author_3_givenName').should('have.value', 'Jane');
         cy.get('#author_3_familyName').should('have.value', '');
         cy.get('#author_3_affiliation').should('have.value', 'Example Org');
     });
 
     it('wraps around to the left', function() {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_add').click();
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
         cy.get('#author_1_affiliation').type('Example Org');
         cy.get('#author_2_givenName').type('John');
         cy.get('#author_2_familyName').type('Doe');
         cy.get('#author_3_givenName').type('Alex');
 
         cy.get('#author_3_moveToRight').click()
 
         cy.get('#author_1_givenName').should('have.value', 'Alex');
         cy.get('#author_1_familyName').should('have.value', '');
         cy.get('#author_1_affiliation').should('have.value', '');
 
         cy.get('#author_2_givenName').should('have.value', 'John');
         cy.get('#author_2_familyName').should('have.value', 'Doe');
         cy.get('#author_2_affiliation').should('have.value', '');
 
         cy.get('#author_3_givenName').should('have.value', 'Jane');
         cy.get('#author_3_familyName').should('have.value', '');
         cy.get('#author_3_affiliation').should('have.value', 'Example Org');
     });
 });
 
 describe('One role', function () {
     it('can be exported in both codemeta v2.0 and v3.0 versions', function () {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
 
         cy.get('#author_1_role_add').click();
         cy.get('#author_1_roleName_0').type('Developer');
         cy.get('#author_1_startDate_0').type('2024-03-04');
         cy.get('#author_1_endDate_0').type('2024-04-03');
 
         cy.get('#generateCodemetaV2').click();
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
+                    {
+                        "type": "Person",
+                        "givenName": "Jane"
+                    },
                     {
                         "type": "schema:Role",
                         "schema:author": {
                             "type": "Person",
                             "givenName": "Jane"
                         },
                         "schema:roleName": "Developer",
                         "schema:startDate": "2024-03-04",
                         "schema:endDate": "2024-04-03"
                     }
                 ]
             });
 
         cy.get('#generateCodemetaV3').click();
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://w3id.org/codemeta/3.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
+                    {
+                        "type": "Person",
+                        "givenName": "Jane"
+                    },
                     {
                         "type": "Role",
                         "schema:author": {
                             "type": "Person",
                             "givenName": "Jane"
                         },
                         "roleName": "Developer",
                         "startDate": "2024-03-04",
                         "endDate": "2024-04-03"
                     }
                 ]
             });
     });
 
     it('and a second one can be exported', function () {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
 
         cy.get('#author_1_role_add').click();
         cy.get('#author_1_roleName_0').type('Developer');
         cy.get('#author_1_startDate_0').type('2024-03-04');
         cy.get('#author_1_endDate_0').type('2024-04-03');
 
         cy.get('#author_1_role_add').click();
         cy.get('#author_1_roleName_1').type('Maintainer');
         cy.get('#author_1_startDate_1').type('2024-04-04');
         cy.get('#author_1_endDate_1').type('2024-05-05');
 
         cy.get('#generateCodemetaV3').click();
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://w3id.org/codemeta/3.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
+                    {
+                        "type": "Person",
+                        "givenName": "Jane"
+                    },
                     {
                         "type": "Role",
                         "schema:author": {
                             "type": "Person",
                             "givenName": "Jane"
                         },
                         "roleName": "Maintainer",
                         "startDate": "2024-04-04",
                         "endDate": "2024-05-05"
                     },
                     {
                         "type": "Role",
                         "schema:author": {
                             "type": "Person",
                             "givenName": "Jane"
                         },
                         "roleName": "Developer",
                         "startDate": "2024-03-04",
                         "endDate": "2024-04-03"
                     }
                 ]
             });
     });
 
     it('can be deleted then added again', function () {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
 
         cy.get('#author_1_role_add').click();
         cy.get('#author_1_roleName_0').type('Developer');
         cy.get('#author_1_startDate_0').type('2024-03-04');
         cy.get('#author_1_endDate_0').type('2024-04-03');
         cy.get('#author_1_role_remove_0').click();
 
         cy.get('#author_1_role_add').click();
         cy.get('#author_1_roleName_1').type('Maintainer');
         cy.get('#author_1_startDate_1').type('2024-04-04');
         cy.get('#author_1_endDate_1').type('2024-05-05');
 
         cy.get('#generateCodemetaV3').click();
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://w3id.org/codemeta/3.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
+                    {
+                        "type": "Person",
+                        "givenName": "Jane"
+                    },
                     {
                         "type": "Role",
                         "schema:author": {
                             "type": "Person",
                             "givenName": "Jane"
                         },
                         "roleName": "Maintainer",
                         "startDate": "2024-04-04",
                         "endDate": "2024-05-05"
                     }
                 ]
             });
     });
 
     it('can be imported', function () {
         // TODO
     });
 });
 
 describe('Multiple authors', function () {
     it('who both have roles can be exported', function () {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
 
         cy.get('#author_add').click();
         cy.get('#author_2_givenName').type('Joe');
 
         cy.get('#author_1_role_add').click();
         cy.get('#author_1_roleName_0').type('Developer');
         cy.get('#author_1_startDate_0').type('2024-03-04');
         cy.get('#author_1_endDate_0').type('2024-04-03');
 
         cy.get('#author_2_role_add').click();
         cy.get('#author_2_roleName_0').type('Maintainer');
         cy.get('#author_2_startDate_0').type('2024-04-04');
         cy.get('#author_2_endDate_0').type('2024-05-05');
 
         cy.get('#generateCodemetaV3').click();
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://w3id.org/codemeta/3.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
+                    {
+                        "type": "Person",
+                        "givenName": "Jane"
+                    },
                     {
                         "type": "Role",
                         "schema:author": {
                             "type": "Person",
                             "givenName": "Jane"
                         },
                         "roleName": "Developer",
                         "startDate": "2024-03-04",
                         "endDate": "2024-04-03"
                     },
+                    {
+                        "type": "Person",
+                        "givenName": "Joe"
+                    },
                     {
                         "type": "Role",
                         "schema:author": {
                             "type": "Person",
                             "givenName": "Joe"
                         },
                         "roleName": "Maintainer",
                         "startDate": "2024-04-04",
                         "endDate": "2024-05-05"
                     }
                 ]
             });
     });
 
     it('whose one has a role and the other not can be exported', function () {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
 
         cy.get('#author_add').click();
         cy.get('#author_2_givenName').type('Joe');
 
         cy.get('#author_1_role_add').click();
         cy.get('#author_1_roleName_0').type('Developer');
         cy.get('#author_1_startDate_0').type('2024-03-04');
         cy.get('#author_1_endDate_0').type('2024-04-03');
 
         cy.get('#generateCodemetaV3').click();
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://w3id.org/codemeta/3.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
+                    {
+                        "type": "Person",
+                        "givenName": "Jane"
+                    },
                     {
                         "type": "Role",
                         "schema:author": {
                             "type": "Person",
                             "givenName": "Jane"
                         },
                         "roleName": "Developer",
                         "startDate": "2024-03-04",
                         "endDate": "2024-04-03"
                     },
                     {
                         "type": "Person",
                         "givenName": "Joe"
                     }
                 ]
             });
     });
 });
diff --git a/js/codemeta_generation.js b/js/codemeta_generation.js
index 3680f43..8a603c7 100644
--- a/js/codemeta_generation.js
+++ b/js/codemeta_generation.js
@@ -1,378 +1,377 @@
 /**
  * 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 LOCAL_CONTEXT_PATH = "./data/contexts/codemeta-local.jsonld";
 const LOCAL_CONTEXT_URL = "local";
 const CODEMETA_CONTEXTS = {
     "2.0": {
         path: "./data/contexts/codemeta-2.0.jsonld",
         url: "https://doi.org/10.5063/schema/codemeta-2.0"
     },
     "3.0": {
         path: "./data/contexts/codemeta-3.0.jsonld",
         url: "https://w3id.org/codemeta/3.0"
     }
 }
 
 const SPDX_PREFIX = 'https://spdx.org/licenses/';
 
 const loadContextData = async () => {
     const [contextLocal, contextV2, contextV3] =
         await Promise.all([
             fetch(LOCAL_CONTEXT_PATH).then(response => response.json()),
             fetch(CODEMETA_CONTEXTS["2.0"].path).then(response => response.json()),
             fetch(CODEMETA_CONTEXTS["3.0"].path).then(response => response.json())
         ]);
     return {
         [LOCAL_CONTEXT_URL]: contextLocal,
         [CODEMETA_CONTEXTS["2.0"].url]: contextV2,
         [CODEMETA_CONTEXTS["3.0"].url]: contextV3
     }
 }
 
 const getJsonldCustomLoader = contexts => {
     return url => {
         const xhrDocumentLoader = jsonld.documentLoaders.xhr();
         if (url in contexts) {
             return {
                 contextUrl: null,
                 document: contexts[url],
                 documentUrl: url
             };
         }
         return xhrDocumentLoader(url);
     }
 };
 
 const initJsonldLoader = contexts => {
     jsonld.documentLoader = getJsonldCustomLoader(contexts);
 };
 
 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',
     'isSourceCodeOf',
     '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',
 ];
 
 const directRoleCodemetaFields = [
     'roleName',
     'startDate',
     'endDate',
 ];
 
 const directReviewCodemetaFields = [
     'reviewAspect',
     'reviewBody'
 ];
 
 const crossedCodemetaFields = {
     "contIntegration": ["contIntegration", "continuousIntegration"],
     "embargoDate": ["embargoDate", "embargoEndDate"],
 };
 
 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",
     }
     var id = getIfSet(`#${idPrefix}_id`);
     if (id !== undefined) {
         doc["@id"] = id;
     }
     directPersonCodemetaFields.forEach(function (item, index) {
         doc[item] = getIfSet(`#${idPrefix}_${item}`);
     });
     doc["affiliation"] = generateShortOrg(`#${idPrefix}_affiliation`);
 
     return doc;
 }
 
 function generateRole(id) {
     const doc = {
         "@type": "Role"
     };
     directRoleCodemetaFields.forEach(function (item, index) {
         doc[item] = getIfSet(`#${id} .${item}`);
     });
     return doc;
 }
 
 function generateRoles(idPrefix, person) {
     const roles = [];
     const roleNodes = document.querySelectorAll(`ul[id^=${idPrefix}_role_`);
     roleNodes.forEach(roleNode => {
         const role = generateRole(roleNode.id);
         role["schema:author"] = person; // Prefix with "schema:" to prevent it from expanding into a list
         roles.push(role);
     });
     return roles;
 }
 
 function generatePersons(prefix) {
     var persons = [];
     var nbPersons = getNbPersons(prefix);
 
     for (let personId = 1; personId <= nbPersons; personId++) {
         const idPrefix = `${prefix}_${personId}`;
         const person = generatePerson(idPrefix);
+        persons.push(person);
         const roles = generateRoles(idPrefix, person);
         if (roles.length > 0) {
             persons = persons.concat(roles);
-        } else {
-            persons.push(person);
         }
     }
 
     return persons;
 }
 
 function generateReview() {
     const doc = {
         "@type": "Review"
     };
     directReviewCodemetaFields.forEach(function (item, index) {
         doc[item] = getIfSet(`#${item}`);
     });
     return doc;
 }
 
 async function buildExpandedJson() {
     var doc = {
         "@context": LOCAL_CONTEXT_URL,
         "@type": "SoftwareSourceCode",
     };
 
     let licenses = getLicenses();
     if (licenses.length > 0) {
         doc["license"] = licenses;
     }
 
     // Generate most fields
     directCodemetaFields.forEach(function (item, index) {
         doc[item] = getIfSet('#' + item)
     });
 
     doc["funder"] = generateShortOrg('#funder', doc["affiliation"]);
 
     const review = generateReview();
     if (review["reviewAspect"] || review["reviewBody"]) {
         doc["review"] = generateReview();
     }
 
     // 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);
         }
     });
 
     // 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;
     }
 
     for (const [key, values] of Object.entries(crossedCodemetaFields)) {
         values.forEach(value => {
            doc[value] = doc[key];
         });
     }
     return await jsonld.expand(doc);
 }
 
 // v2.0 is still default version for generation, for now
 async function generateCodemeta(codemetaVersion = "2.0") {
     var inputForm = document.querySelector('#inputForm');
     var codemetaText, errorHTML;
 
     if (inputForm.checkValidity()) {
         const expanded = await buildExpandedJson();
         const compacted = await jsonld.compact(expanded, CODEMETA_CONTEXTS[codemetaVersion].url);
         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, getDocumentId(doc));
     }
 }
 
 function importPersons(prefix, legend, docs) {
     if (docs === undefined) {
         return;
     }
 
     docs.forEach(function (doc, index) {
         var personId = addPerson(prefix, legend);
 
         setIfDefined(`#${prefix}_${personId}_id`, getDocumentId(doc));
         directPersonCodemetaFields.forEach(function (item, index) {
             setIfDefined(`#${prefix}_${personId}_${item}`, doc[item]);
         });
 
         importShortOrg(`#${prefix}_${personId}_affiliation`, doc['affiliation'])
     })
 }
 
 async function importCodemeta() {
     var inputForm = document.querySelector('#inputForm');
     var doc = await 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();
     }
 }