diff --git a/README.md b/README.md
index 8093a47..45d1857 100644
--- a/README.md
+++ b/README.md
@@ -1,103 +1,103 @@
 # Codemeta Generator
 
 This repository contains a (client-side) web application to generate
 CodeMeta documents (aka. `codemeta.json`).
 
 The [CodeMeta initiative](https://github.com/codemeta/codemeta) is a Free and Open Source academic collaboration
 creating a minimal metadata schema for research software and code.
 
 The academic community recommands on adding a codemeta.json file in
 the root directory of your repository.
 
 With this linked data metadata file, you can easily declare the authorship,
 include contextual information and link to other research outputs (publications,
 data, etc.).
 
 Also, the `codemeta.json` file in your source code is indexed in the
 Software Heritage (SWH) archive, which will improve findability in searches.
 
 ### References
 
 - [SWH guidelines](https://www.softwareheritage.org/save-and-reference-research-software/) for research software.
 
 - [SWH blog post](https://www.softwareheritage.org/2019/05/28/mining-software-metadata-for-80-m-projects-and-even-more/) about metadata indexation.
 - [Dan S. Katz's blog post](https://danielskatzblog.wordpress.com/2017/09/25/software-heritage-and-repository-metadata-a-software-citation-solution/) about including
  metadata in your repository.
 - FORCE11's Software Citation Implementation WG [repository](https://github.com/force11/force11-sciwg)
 - RDA & FORCE11's joint Software Source Code Identification WG
    [repository](https://github.com/force11/force11-rda-scidwg)
 
 ## Specifications
 
 ### Use case
 
 1. create a complete codemeta.json file from scratch
 2. aggregate existing information and add complementary information to
 a codemeta.json file
 
 ### Functionalities
 
 - helpers while completing the form, for example a reference list of spdx
   licenses
 - a validation mechanism after submission
 - the possibility to use all the codeMeta terms and schema.org terms
 - accessible from multiple platforms (web browsers or OS)
 - (extra) the possibility to correct the output after validation as part
   of the creation process
 
 This tool was initially prepared for the [FORCE19 Hackathon](https://github.com/force11/force11-rda-scidwg/tree/master/hackathon/FORCE2019).
 
+**NB:** codemeta v2.0 is generated by default, but v3.0 (v2.0 compatible) can be generated via a dedicated button.
 
 ## Code contributions.
 
 This section only applies to developers who want to contribute to the Codemeta Generator.
 If you only want to use it, you can use
 [the hosted version](https://codemeta.github.io/codemeta-generator/) instead.
 
 ### Code guidelines
 
 This application is designed to work on popular modern browsers (Firefox,
 Chromium/Google Chrome, Edge, Safari). Check [Caniuse](https://caniuse.com/)
 for availability of features for these browsers.
 
 To keep the architecture simple, we serve javascript files directly to
-browsers, without a compiler or transpiler; and do not use third-party
-dependencies for now.
+browsers, without a compiler or transpiler.
 
 ### Running local changes
 
 To run Codemeta Generator, you just need an HTTP server serving the
 files (nginx, apache2, etc.).
 
 The simplest way is probably to use Python's HTTP server:
 
 ```
 git clone https://github.com/codemeta/codemeta-generator
 cd codemeta-generator
 python3 -m http.server
 ```
 
 then open [http://localhost:8000/](http://localhost:8000/) in your web browser.
 
 ### Automatic testing
 
 In addition to manual testing, we have automated tests to check for bugs
 quickly, using [Cypress](https://www.cypress.io/).
 
 To run them, first install Cypress:
 
 ```
 sudo apt install npm  # or the equivalent on your system
 npx cypress@9.7.0 install
 ```
 
 Then, run the tests:
 
 ```
 npx cypress@9.7.0 run
 ```
 
 
 ## Contributed by
 
 ![Image description](https://annex.softwareheritage.org/public/logo/software-heritage-logo-title-motto.svg)
diff --git a/cypress/integration/basics.js b/cypress/integration/basics.js
index 4e1e575..2cfd994 100644
--- a/cypress/integration/basics.js
+++ b/cypress/integration/basics.js
@@ -1,267 +1,364 @@
 /**
  * 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 basic features of the application.
  */
 
 "use strict";
 
 describe('JSON Generation', function() {
     beforeEach(function() {
         /* Clear the session storage, as it is used to restore field data;
          * and we don't want a test to load data from the previous test. */
         cy.window().then((win) => {
             win.sessionStorage.clear()
         })
         cy.visit('./index.html');
     });
 
     it('works just from the software name', 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('works just from all main fields when using only one license', function() {
         cy.get('#name').type('My Test Software');
         cy.get('#description').type('This is a\ngreat piece of software');
         cy.get('#dateCreated').type('2019-10-02');
         cy.get('#datePublished').type('2020-01-01');
         cy.get('#license').type('AGPL-3.0');
         cy.get("#license").type('{enter}');
         cy.get('#generateCodemetaV2').click();
 
         cy.get("#license").should('have.value', '');
         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",
                 "license": "https://spdx.org/licenses/AGPL-3.0",
                 "dateCreated": "2019-10-02",
                 "datePublished": "2020-01-01",
                 "name": "My Test Software",
                 "description": "This is a\ngreat piece of software",
         });
     });
 
     it('works just from all main fields when using multiple licenses', function() {
         cy.get('#name').type('My Test Software');
         cy.get('#description').type('This is a\ngreat piece of software');
         cy.get('#dateCreated').type('2019-10-02');
         cy.get('#datePublished').type('2020-01-01');
         cy.get('#license').type('AGPL-3.0');
         cy.get("#license").type('{enter}');
         cy.get('#license').type('MIT');
         cy.get("#license").type('{enter}');
 
         cy.get('#generateCodemetaV2').click();
 
         cy.get("#license").should('have.value', '');
         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",
                 "license": ["https://spdx.org/licenses/AGPL-3.0", "https://spdx.org/licenses/MIT"],
                 "dateCreated": "2019-10-02",
                 "datePublished": "2020-01-01",
                 "name": "My Test Software",
                 "description": "This is a\ngreat piece of software",
         });
     });
 
     it('works when choosing licenses without the keyboard', function() {
         cy.get('#name').type('My Test Software');
         cy.get('#description').type('This is a\ngreat piece of software');
         cy.get('#dateCreated').type('2019-10-02');
         cy.get('#datePublished').type('2020-01-01');
         cy.get('#license').type('AGPL-3.0');
         // no cy.get("#license").type('{enter}'); here
         cy.get('#generateCodemetaV2').click();
 
         cy.get("#license").should('have.value', '');
         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",
                 "license": "https://spdx.org/licenses/AGPL-3.0",
                 "dateCreated": "2019-10-02",
                 "datePublished": "2020-01-01",
                 "name": "My Test Software",
                 "description": "This is a\ngreat piece of software",
         });
     });
 
     it('works for new codemeta terms in both versions', function() {
         cy.get('#name').type('My Test Software');
         cy.get('#contIntegration').type('https://test-ci.org/my-software');
         cy.get('#isSourceCodeOf').type('Bigger Application');
         cy.get('#reviewAspect').type('Some software aspect');
         cy.get('#reviewBody').type('Some review');
 
         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",
                 "contIntegration": "https://test-ci.org/my-software",
                 "codemeta:continuousIntegration": {
                     "id": "https://test-ci.org/my-software"
                 },
                 "codemeta:isSourceCodeOf": {
                     "id": "Bigger Application"
                 },
                 "schema:review": {
                     "type": "schema:Review",
                     "schema:reviewAspect": "Some software aspect",
                     "schema:reviewBody": "Some review"
                 }
         });
 
         cy.get('#generateCodemetaV3').click();
         cy.get('#errorMessage').should('have.text', '');
         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",
                 "continuousIntegration": "https://test-ci.org/my-software",
                 "codemeta:contIntegration": {
                     "id": "https://test-ci.org/my-software"
                 },
                 "isSourceCodeOf": "Bigger Application",
                 "review": {
                     "type": "Review",
                     "reviewAspect": "Some software aspect",
                     "reviewBody": "Some review"
                 }
         });
     });
 });
 
 describe('JSON Import', function() {
     it('works just from the software name', 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('#name').should('have.value', 'My Test Software');
     });
 
     it('works just from all main fields when using license as string', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "@type": "SoftwareSourceCode",
                 "license": "https://spdx.org/licenses/AGPL-3.0",
                 "dateCreated": "2019-10-02",
                 "datePublished": "2020-01-01",
                 "name": "My Test Software",
                 "description": "This is a\ngreat piece of software",
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#name').should('have.value', 'My Test Software');
         cy.get('#description').should('have.value', 'This is a\ngreat piece of software');
         cy.get('#dateCreated').should('have.value', '2019-10-02');
         cy.get('#datePublished').should('have.value', '2020-01-01');
         cy.get('#license').should('have.value', '');
         cy.get("#selected-licenses").children().should('have.length', 1);
         cy.get("#selected-licenses").children().first().children().first().should('have.text', 'AGPL-3.0');
     });
 
     it('works just from all main fields when using license as array', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "@type": "SoftwareSourceCode",
                 "license": ["https://spdx.org/licenses/AGPL-3.0", "https://spdx.org/licenses/MIT"],
                 "dateCreated": "2019-10-02",
                 "datePublished": "2020-01-01",
                 "name": "My Test Software",
                 "description": "This is a\ngreat piece of software",
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#name').should('have.value', 'My Test Software');
         cy.get('#description').should('have.value', 'This is a\ngreat piece of software');
         cy.get('#dateCreated').should('have.value', '2019-10-02');
         cy.get('#datePublished').should('have.value', '2020-01-01');
         cy.get('#license').should('have.value', '');
         cy.get("#selected-licenses").children().should('have.length', 2);
         cy.get("#selected-licenses").children().eq(0).children().first().should('have.text', 'AGPL-3.0');
         cy.get("#selected-licenses").children().eq(1).children().first().should('have.text', 'MIT');
     });
 
     it('works with expanded document version', function () {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "http://schema.org/name": [
                     {
                         "@value": "My Test Software"
                     }
                 ],
                 "@type": [
                     "http://schema.org/SoftwareSourceCode"
                 ]
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#name').should('have.value', 'My Test Software');
     });
 
     it('errors on invalid type', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "@type": "foo",
                 "name": "My Test Software",
             }))
         );
         cy.get('#importCodemeta').click();
 
         // Should still be imported as much as possible
         cy.get('#name').should('have.value', 'My Test Software');
 
         // But must display an error
         cy.get('#errorMessage').should('have.text', 'Wrong document type: must be "SoftwareSourceCode"/"SoftwareApplication", not "foo"');
     });
 
     it('allows singleton array as context', 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('#name').should('have.value', 'My Test Software');
     });
 
+    it('imports properties introduced in codemeta v3.0', function() {
+        cy.get('#codemetaText').then((elem) =>
+            elem.text(JSON.stringify({
+                "@context": "https://w3id.org/codemeta/3.0",
+                "type": "SoftwareSourceCode",
+                "name": "My Test Software",
+                "continuousIntegration": "https://test-ci.org/my-software",
+                "isSourceCodeOf": "Bigger Application",
+                "review": {
+                    "type": "Review",
+                    "reviewAspect": "Some software aspect",
+                    "reviewBody": "Some review"
+                }
+            }))
+        );
+        cy.get('#importCodemeta').click();
+
+        cy.get('#contIntegration').should('have.value', 'https://test-ci.org/my-software');
+        cy.get('#isSourceCodeOf').should('have.value', 'Bigger Application');
+        cy.get('#reviewAspect').should('have.value', 'Some software aspect');
+        cy.get('#reviewBody').should('have.value', 'Some review');
+    });
+
+    it('imports codemeta v2.0 properties from document with v3.0 context', function() {
+        cy.get('#codemetaText').then((elem) =>
+            elem.text(JSON.stringify({
+                "@context": "https://w3id.org/codemeta/3.0",
+                "type": "SoftwareSourceCode",
+                "name": "My Test Software",
+                "codemeta:contIntegration": {
+                    "id": "https://test-ci.org/my-software"
+                }
+            }))
+        );
+        cy.get('#importCodemeta').click();
+
+        cy.get('#contIntegration').should('have.value', 'https://test-ci.org/my-software');
+    });
+
+    it('imports codemeta v3.0 properties from document with v2.0 context', 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",
+                "codemeta:continuousIntegration": {
+                    "id": "https://test-ci.org/my-software"
+                },
+                "codemeta:isSourceCodeOf": {
+                    "id": "Bigger Application"
+                },
+                "schema:review": {
+                    "type": "schema:Review",
+                    "schema:reviewAspect": "Some software aspect",
+                    "schema:reviewBody": "Some review"
+                }
+            }))
+        );
+        cy.get('#importCodemeta').click();
+
+        cy.get('#contIntegration').should('have.value', 'https://test-ci.org/my-software');
+        cy.get('#isSourceCodeOf').should('have.value', 'Bigger Application');
+        cy.get('#reviewAspect').should('have.value', 'Some software aspect');
+        cy.get('#reviewBody').should('have.value', 'Some review');
+    });
+
+    it('imports newest version property when it is duplicate in multiple version context', 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",
+                "contIntegration": "https://test-ci1.org/my-software",
+                "codemeta:continuousIntegration": {
+                    "id": "https://test-ci2.org/my-software"
+                },
+            }))
+        );
+        cy.get('#importCodemeta').click();
+
+        cy.get('#contIntegration').should('have.value', 'https://test-ci2.org/my-software');
+
+        cy.get('#codemetaText').then((elem) =>
+            elem.text(JSON.stringify({
+                "@context": "https://doi.org/10.5063/schema/codemeta-3.0",
+                "type": "SoftwareSourceCode",
+                "name": "My Test Software",
+                "continuousIntegration": "https://test-ci1.org/my-software",
+                "codemeta:contIntegration": {
+                    "id": "https://test-ci2.org/my-software"
+                },
+            }))
+        );
+        cy.get('#importCodemeta').click();
+
+        cy.get('#contIntegration').should('have.value', 'https://test-ci1.org/my-software');
+    });
 });
diff --git a/cypress/integration/persons.js b/cypress/integration/persons.js
index 7516167..b3b68ce 100644
--- a/cypress/integration/persons.js
+++ b/cypress/integration/persons.js
@@ -1,686 +1,850 @@
 /**
  * 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() {
+    it('can be imported even if there is also a role-less author', 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 author with a 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('can have two roles', 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
+        cy.get('#codemetaText').then((elem) =>
+            elem.text(JSON.stringify({
+                "@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"
+                    }
+                ]
+            }))
+        );
+        cy.get('#importCodemeta').click();
+
+        cy.get('#author_nb').should('have.value', '1');
+        cy.get('#author_1_givenName').should('have.value', 'Jane');
+        cy.get('#author_1_roleName_0').should('have.value', 'Developer');
+        cy.get('#author_1_startDate_0').should('have.value', '2024-03-04');
+        cy.get('#author_1_endDate_0').should('have.value', '2024-04-03');
+    });
+
+    it('can be imported when there is a second one, and they are merged', function () {
+        cy.get('#codemetaText').then((elem) =>
+            elem.text(JSON.stringify({
+                "@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"
+                    }
+                ]
+            }))
+        );
+        cy.get('#importCodemeta').click();
+
+        cy.get('#author_nb').should('have.value', '1');
+        cy.get('#author_1_givenName').should('have.value', 'Jane');
+        cy.get('#author_1_roleName_0').should('have.value', 'Maintainer');
+        cy.get('#author_1_startDate_0').should('have.value', '2024-04-04');
+        cy.get('#author_1_endDate_0').should('have.value', '2024-05-05');
+        cy.get('#author_1_roleName_1').should('have.value', 'Developer');
+        cy.get('#author_1_startDate_1').should('have.value', '2024-03-04');
+        cy.get('#author_1_endDate_1').should('have.value', '2024-04-03');
     });
 });
 
 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"
                     }
                 ]
             });
     });
+
+    it('who both have roles can be imported', function () {
+        cy.get('#codemetaText').then((elem) =>
+            elem.text(JSON.stringify({
+                 "@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"
+                    }
+                ]
+            }))
+        );
+        cy.get('#importCodemeta').click();
+
+        cy.get('#author_nb').should('have.value', '2');
+        cy.get('#author_1_givenName').should('have.value', 'Jane');
+        cy.get('#author_1_roleName_0').should('have.value', 'Developer');
+        cy.get('#author_1_startDate_0').should('have.value', '2024-03-04');
+        cy.get('#author_1_endDate_0').should('have.value', '2024-04-03');
+        cy.get('#author_2_givenName').should('have.value', 'Joe');
+        cy.get('#author_2_roleName_0').should('have.value', 'Maintainer');
+        cy.get('#author_2_startDate_0').should('have.value', '2024-04-04');
+        cy.get('#author_2_endDate_0').should('have.value', '2024-05-05');
+    });
+
+    it('whose one has a role and the other not can be imported', function () {
+        cy.get('#codemetaText').then((elem) =>
+            elem.text(JSON.stringify({
+                 "@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"
+                    }
+                ]
+            }))
+        );
+        cy.get('#importCodemeta').click();
+
+        cy.get('#author_nb').should('have.value', '2');
+        cy.get('#author_1_givenName').should('have.value', 'Jane');
+        cy.get('#author_1_roleName_0').should('have.value', 'Developer');
+        cy.get('#author_1_startDate_0').should('have.value', '2024-03-04');
+        cy.get('#author_1_endDate_0').should('have.value', '2024-04-03');
+        cy.get('#author_2_givenName').should('have.value', 'Joe');
+    });
 });
diff --git a/index.html b/index.html
index 3af9815..cda6dc8 100644
--- a/index.html
+++ b/index.html
@@ -1,394 +1,394 @@
 <!doctype html>
 <!--
 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
 -->
 <html lang="en">
 <head>
     <meta charset="utf-8">
     <title>CodeMeta generator</title>
     <script src="./js/utils.js"></script>
     <script src="./js/fields_data.js"></script>
     <script src="./js/dynamic_form.js"></script>
     <script src="./js/codemeta_generation.js"></script>
     <script src="./js/validation/primitives.js"></script>
     <script src="./js/validation/things.js"></script>
     <script src="./js/validation/index.js"></script>
     <link rel="stylesheet" type="text/css" href="./main.css">
     <link rel="stylesheet" type="text/css" href="./codemeta.css">
 </head>
 <body>
   <header>
-    <h1>CodeMeta generator v3.0</h1><!-- Generate codemeta v2.0 by default for now, allow v3.0 generation by clicking -->
+    <h1>CodeMeta generator v3.0</h1>
   </header>
 
   <main>
 
     <p>Most fields are optional. Mandatory fields will be highlighted when generating Codemeta.</p>
 
     <noscript>
         <p id="noscriptError">
             This application requires Javascript to show dynamic fields in the form,
             and generate a JSON file; but your browser does not support Javascript.
             If you cannot use a browser with Javascript support, you can try
             <a href="https://codemeta.github.io/tools/">one of the other available tools</a>
             or write the codemeta.json file directly.
         </p>
     </noscript>
 
     <form id="inputForm">
         <fieldset id="fieldsetSoftwareItself" class="leafFieldset">
             <legend>The software itself</legend>
 
             <p title="The name of the software">
                 <label for="name">Name</label>
                 <input type="text" name="name" id="name" aria-describedby="name_descr"
                     placeholder="My Software" required="required" />
 
                 <span class="field-description" id="name_descr">the software title</span>
             </p>
 
             <p title="a brief description of the software">
                 <label for="description">Description</label>
                 <textarea rows="4" cols="50"
                     name="description" id="description"
                     placeholder="My Software computes ephemerides and orbit propagation. It has been developed from early ´80." ></textarea>
             </p>
 
 
             <p title="The date on which the software was created.">
                 <label for="dateCreated">Creation date</label>
                 <input type="text" name="dateCreated" id="dateCreated"
                     placeholder="YYYY-MM-DD" pattern="\d{4}-\d{2}-\d{2}" />
             </p>
 
             <p title="Date of first publication.">
                 <label for="datePublished">First release date</label>
                 <input type="text" name="datePublished" id="datePublished"
                     placeholder="YYYY-MM-DD" pattern="\d{4}-\d{2}-\d{2}" />
             </p>
 
             <p>
                 <label for="license">License(s)</label>
                 <input list="licenses" name="license" id="license"
                     aria-describedby="licenses_descr"> <!-- TODO: insert placeholder -->
 
                 <datalist id="licenses">
                 </datalist>
                 <!-- This datalist is be filled automatically -->
 
                 <br />
                 <span class="field-description" id="licenses_descr">from <a href="https://spdx.org/license-list">SPDX licence list</a></span>
 
                 <div id="selected-licenses">
                     <!-- This div is to be filled as the user selects licenses -->
                 </div>
             </p>
         </fieldset>
 
         <fieldset id="fieldsetDiscoverabilityAndCitation" class="leafFieldset">
             <legend>Discoverability and citation</legend>
 
             <p title="Unique identifier">
                 <label for="identifier">Unique identifier</label>
                 <input type="text" name="identifier" id="identifier"
                     placeholder="10.151.xxxxx" aria-describedby="identifier_descr" />
 
                 <br />
                 <span class="field-description" id="identifier_descr">
                     such as ISBNs, GTIN codes, UUIDs etc..  <a href="http://schema.org/identifier">http://schema.org/identifier</a>
                 </span>
             </p>
             <!-- TODO:define better
 
             I looked at the schema.org definition of identifier (https://schema.org/identifier),
             it can be text, url or PropertyValue.
             Used as follows in data representation with microdata:
 
             <div property="identifier" typeof="PropertyValue">
             <span property="propertyID">DOI</span>:
             <span property="value">10.151.xxxxx</span>
             </div>
             we can use that with identifier-type and identifier-value to have a clearer idea
             of what needs to be in the input.
 
             -->
 
             <p title="Type of the software application">
                 <label for="applicationCategory">Application category</label>
                 <input type="text" name="applicationCategory" id="applicationCategory"
                     placeholder="Astronomy"  />
             </p>
 
             <p title="Comma-separated list of keywords">
                 <label for="keywords">Keywords</label>
                 <input type="text" name="keywords" id="keywords"
                     placeholder="ephemerides, orbit, astronomy"  />
             </p>
 
             <p title="Funding / grant">
                 <label for="funding">Funding</label>
                 <input type="text" name="funding" id="funding" aria-describedby="funding_descr"
                     placeholder="PRA_2018_73"/>
 
                 <br />
                 <span class="field-description" id="funding_descr">grant funding software development</span>
             </p>
 
             <p title="Funding / organization">
                 <label for="funder">Funder</label>
                 <input type="text" name="funder" id="funder" aria-describedby="funder_descr"
                     placeholder="Università di Pisa"/>
 
                 <br />
                 <span class="field-description" id="funder_descr">organization funding software development</span>
             </p>
 
             Authors and contributors can be added below
         </fieldset>
 
         <fieldset id="fieldsetDevelopmentCommunity" class="leafFieldset">
             <legend>Development community / tools</legend>
 
             <p title="Link to the repository where the un-compiled, human readable code and related code is located (SVN, Git, GitHub, CodePlex, institutional GitLab instance, etc.).">
                 <label for="codeRepository">Code repository</label>
                 <input type="URL" name="codeRepository" id="codeRepository"
                     placeholder="git+https://github.com/You/RepoName.git" />
             </p>
 
             <p title="Link to continuous integration service (Travis-CI, Gitlab CI, etc.).">
                 <label for="contIntegration">Continuous integration</label>
                 <input type="URL" name="contIntegration" id="contIntegration"
                     placeholder="https://travis-ci.org/You/RepoName" />
             </p>
 
             <p title="Link to a place for users/developpers to report and manage bugs (JIRA, GitHub issues, etc.).">
                 <label for="issueTracker">Issue tracker</label>
                 <input type="URL" name="issueTracker" id="issueTracker"
                     placeholder="https://github.com/You/RepoName/issues" />
             </p>
 
             <p title="Related document, software, tools">
                 <label for="relatedLink">Related links</label>
                 <br />
                 <textarea rows="4" cols="50"
                     name="relatedLink" id="relatedLink"></textarea>
         </fieldset>
 
         <fieldset id="fieldsetRuntime" class="leafFieldset">
             <legend>Run-time environment</legend>
 
             <p title="Programming Languages, separated by commas">
                 <label for="programmingLanguage">Programming Language</label>
                 <input type="text" name="programmingLanguage" id="programmingLanguage"
                     placeholder="C#, Java, Python 3"  />
             </p>
 
             <p title="Runtime Platforms, separated by commas">
                 <label for="runtimePlatform">Runtime Platform</label>
                 <input type="text" name="runtimePlatform" id="runtimePlatform"
                     placeholder=".NET, JVM" />
             </p>
 
             <p title="Operating Systems, separated by commas">
                 <label for="operatingSystem">Operating System</label>
                 <input type="text" name="operatingSystem" id="operatingSystem"
                     placeholder="Android 1.6, Linux, Windows, macOS" />
             </p>
 
             <p title="Required software to run/use this one.">
                 <label for="softwareRequirements">Other software requirements</label>
                 <br />
                 <textarea rows="4" cols="50"
                     name="softwareRequirements" id="softwareRequirements"
                     placeholder=
 "Python 3.4
 https://github.com/psf/requests"></textarea>
         </fieldset>
 
         <fieldset id="fieldsetCurrentVersion" class="leafFieldset">
             <legend>Current version of the software</legend>
 
             <p title="Version number of the software">
                 <label for="version">Version number</label>
                 <input type="text" name="version" id="version"
                     placeholder="1.0.0" />
             </p>
 
             <p title="The date on which the software was most recently modified.">
                 <label for="dateModified">Release date</label>
                 <input type="text" name="dateModified" id="dateModified"
                     placeholder="YYYY-MM-DD" pattern="\d{4}-\d{2}-\d{2}" />
             </p>
 
             <p title="Download link">
                 <label for="downloadUrl">Download URL</label>
                 <input type="URL" name="downloadUrl" id="downloadUrl"
                     placeholder="https://example.org/MySoftware.tar.gz" />
             </p>
 
             <p title="a brief description of the software">
                 <label for="releaseNotes">Release notes</label>
                 <br />
                 <textarea rows="4" cols="50"
                     name="releaseNotes" id="releaseNotes"
                     placeholder=
 "Change log: this and that;
 Bugfixes: that and this." ></textarea>
             </p>
 
 <!--TODO: referencePublication as ScholarlyArticle array -->
 
         </fieldset>
 
         <fieldset id="review_container" class="leafFieldset">
             <legend>Editorial review</legend>
 
             <p title="Scholarly article describing this software">
                 <label for="referencePublication">Reference Publication</label>
                 <input type="URL" name="referencePublication" id="referencePublication"
                     placeholder="https://doi.org/10.1000/xyz123" />
             </p>
 
             <p title="Part or facet of the object being review ">
                 <label for="reviewAspect">Review aspect</label>
                 <input type="text" name="reviewAspect" id="reviewAspect"
                     placeholder="Object facet" />
             </p>
 
           <p title="The actual body of the review ">
                 <label for="reviewBody">Review body</label>
                 <textarea rows="4" cols="50"
                     name="reviewBody" id="reviewBody"
                     placeholder="Review about my software." ></textarea>
             </p>
         </fieldset>
 
         <fieldset id="fieldsetAdditionalInfo" class="leafFieldset">
             <legend>Additional Info</legend>
 
             <p title="Development Status">
             <label for="developmentStatus">Development Status</label>
                 <datalist id="developmentStatuses">
                         <option value="concept">
                         <option value="wip">
                         <option value="suspended">
                         <option value="abandoned">
                         <option value="active">
                         <option value="inactive">
                         <option value="unsupported">
                         <option value="moved">
                 </datalist>
                 <input list="developmentStatuses" id="developmentStatus" aria-describedby="developmentStatuses_descr"
                 pattern="concept|wip|suspended|abandoned|active|inactive|unsupported|moved">
 
                 <br />
                 <span class="field-description" id="developmentStatuses_descr">
                     see <a href="http://www.repostatus.org">www.repostatus.org</a> for details
                 </span>
             </p>
 
             <p title="Source Code of">
                 <label for="isSourceCodeOf">Is Source Code of</label>
                 <input type="text" name="isSourceCodeOf" id="isSourceCodeOf"
                     placeholder="Bigger Application" />
             </p>
 
             <p title="Part of">
                 <label for="isPartOf">Is part of</label>
                 <input type="URL" name="isPartOf" id="isPartOf"
                     placeholder="http://The.Bigger.Framework.org"  />
             </p>
         </fieldset>
 
         <div class="dynamicFields">
             <fieldset class="persons" id="author_container">
                 <legend>Authors</legend>
 
                 <input type="hidden" id="author_nb" value="0" />
                 <div id="addRemoveAuthor">
                     <input type="button" id="author_add" value="Add one"
                         onclick="addPerson('author', 'Author');" />
                     <input type="button" id="author_remove" value="Remove last"
                         onclick="removePerson('author');" />
                 </div>
             </fieldset>
 
             <fieldset class="persons" id="contributor_container">
                 <legend>Contributors</legend>
 
                 <p>Order of contributors does not matter.</p>
 
                 <input type="hidden" id="contributor_nb" value="0" />
                 <div id="addRemoveContributor">
                     <input type="button" id="contributor_add" value="Add one"
                         onclick="addPerson('contributor', 'Contributor');" />
                     <input type="button" id="contributor_remove" value="Remove last"
                         onclick="removePerson('contributor');" />
                 </div>
             </fieldset>
         </div>
 
     </form>
     <form>
       <input type="button" id="generateCodemetaV3" value="Generate codemeta.json v3.0" disabled
              title="Creates a codemeta.json v3.0 file below, from the information provided above." />
       <input type="button" id="generateCodemetaV2" value="Generate codemeta.json v2.0" disabled
             title="Creates a codemeta.json v2.0 file below, from the information provided above." />
         <input type="button" id="resetForm" value="Reset form"
             title="Erases all fields." />
         <input type="button" id="validateCodemeta" value="Validate codemeta.json" disabled
             title="Checks the codemeta.json file below is valid, and displays errors." />
         <input type="button" id="importCodemeta" value="Import codemeta.json" disabled
             title="Fills the fields above based on the codemeta.json file below." />
     </form>
 
     <p id="errorMessage">
     </p>
     <p>codemeta.json:</p>
     <pre contentEditable="true" id="codemetaText"></pre>
   </main>
 
   <footer>
     <p style="text-align:center;">
         Do you want to improve this tool ?
         Check out the
         <a href="https://github.com/codemeta/codemeta-generator">
             CodeMeta-generator repository</a>
         <br />
         Join the
         <a href="https://github.com/codemeta/codemeta">CodeMeta community</a>
         discussion
         <br />
         The CodeMeta vocabulary -
         <a href="https://doi.org/10.5063/schema/codemeta-2.0">v2.0</a>
         -
         <a href="https://w3id.org/codemeta/3.0">v3.0</a>
     </p>
 
     <h2 style="text-align:right;">Contributed by</h2>
     <p style="text-align:right;">
         <a href="https://www.softwareheritage.org/save-and-reference-research-software/">
             <img alt="Software Heritage" src="https://annex.softwareheritage.org/public/logo/software-heritage-logo-title-motto.svg"
                 width="300">
         </a>
     </p>
   </footer>
 
   <script src="./js/libs/jsonld/jsonld.min.js"></script>
   <script>
     Promise.all([loadSpdxData(), loadContextData()]).then(results => {
       const [licenses, contexts] = results;
       SPDX_LICENSES = licenses;
       SPDX_LICENSE_IDS = licenses.map(license => license['licenseId']);
 
       initJsonldLoader(contexts);
       initFields();
       initCallbacks();
       loadStateFromStorage();
     });
   </script>
 </body>
 </html>
diff --git a/js/codemeta_generation.js b/js/codemeta_generation.js
index 8a603c7..6dd111d 100644
--- a/js/codemeta_generation.js
+++ b/js/codemeta_generation.js
@@ -1,377 +1,437 @@
 /**
  * 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 = {
+const crossCodemetaFields = {
     "contIntegration": ["contIntegration", "continuousIntegration"],
-    "embargoDate": ["embargoDate", "embargoEndDate"],
+    // "embargoDate": ["embargoDate", "embargoEndDate"], Not present in the form yet TODO ?
 };
 
 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);
         }
     }
 
     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];
+    for (const [key, items] of Object.entries(crossCodemetaFields)) {
+        items.forEach(item => {
+           doc[item] = 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 importReview(doc) {
+    if (doc !== undefined) {
+        directReviewCodemetaFields.forEach(item => {
+            setIfDefined('#' + item, doc[item]);
+        });
+    }
+}
+
+function authorsEqual(author1, author2) {
+    // TODO should test more properties for equality?
+    return author1.givenName === author2.givenName
+        && author1.familyName === author2.familyName
+        && author1.email === author2.email;
+}
+
+function getSingleAuthorsFromRoles(docs) {
+    return docs.filter(doc => getDocumentType(doc) === "Role")
+        .map(doc => doc["schema:author"])
+        .reduce((authorSet, currentAuthor) => {
+            const foundAuthor = authorSet.find(author => authorsEqual(author, currentAuthor));
+            if (!foundAuthor) {
+                return authorSet.concat([currentAuthor]);
+            } else {
+                return authorSet;
+            }
+        }, []);
+}
+
+function importRoles(personPrefix, roles) {
+    roles.forEach(role => {
+        const roleId = addRole(`${personPrefix}`);
+        directRoleCodemetaFields.forEach(item => {
+            setIfDefined(`#${personPrefix}_${item}_${roleId}`, role[item]);
+        });
+    });
+}
+
 function importPersons(prefix, legend, docs) {
     if (docs === undefined) {
         return;
     }
 
-    docs.forEach(function (doc, index) {
+    const authors = docs.filter(doc => getDocumentType(doc) === "Person");
+    const authorsFromRoles = getSingleAuthorsFromRoles(docs);
+    const allAuthorDocs = authors.concat(authorsFromRoles)
+        .reduce((authors, currentAuthor) => {
+            if (!authors.find(author => authorsEqual(author, currentAuthor))) {
+                authors.push(currentAuthor);
+            }
+            return authors;
+        }, []);
+
+    allAuthorDocs.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'])
-    })
+        importShortOrg(`#${prefix}_${personId}_affiliation`, doc['affiliation']);
+
+        const roles = docs.filter(currentDoc => getDocumentType(currentDoc) === "Role")
+            .filter(currentDoc => authorsEqual(currentDoc["schema:author"], doc));
+        importRoles(`${prefix}_${personId}`, roles);
+    });
 }
 
 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"]);
+    importReview(doc["review"]);
 
     // 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);
         }
     });
 
+    for (const [key, items] of Object.entries(crossCodemetaFields)) {
+        let value = "";
+        items.forEach(item => {
+           value = doc[item] || value;
+        });
+        setIfDefined(`#${key}`, 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();
     }
 }
diff --git a/js/dynamic_form.js b/js/dynamic_form.js
index 7f43bd2..f15aba8 100644
--- a/js/dynamic_form.js
+++ b/js/dynamic_form.js
@@ -1,230 +1,232 @@
 /**
  * Copyright (C) 2019  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";
 
 // List of all HTML fields in a Person fieldset.
 const personFields = [
     'givenName',
     'familyName',
     'email',
     'id',
     'affiliation',
 ];
 
 function createPersonFieldset(personPrefix, legend) {
     // Creates a fieldset containing inputs for informations about a person
     var fieldset = document.createElement("fieldset")
     var moveButtons;
     fieldset.classList.add("person");
     fieldset.classList.add("leafFieldset");
     fieldset.id = personPrefix;
 
     fieldset.innerHTML = `
         <legend>${legend}</legend>
         <div class="moveButtons">
             <input type="button" id="${personPrefix}_moveToLeft" value="<" class="moveToLeft"
                 title="Moves this person to the left." />
             <input type="button" id="${personPrefix}_moveToRight" value=">" class="moveToRight"
                 title="Moves this person to the right." />
         </div>
         <p>
             <label for="${personPrefix}_givenName">Given name</label>
             <input type="text" id="${personPrefix}_givenName" name="${personPrefix}_givenName"
                 placeholder="Jane" required="true" />
         </p>
         <p>
             <label for="${personPrefix}_familyName">Family name</label>
             <input type="text" id="${personPrefix}_familyName" name="${personPrefix}_familyName"
                 placeholder="Doe" />
         </p>
         <p>
             <label for="${personPrefix}_email">E-mail address</label>
             <input type="email" id="${personPrefix}_email" name="${personPrefix}_email"
                 placeholder="jane.doe@example.org" />
         </p>
         <p>
             <label for="${personPrefix}_id">URI</label>
             <input type="url" id="${personPrefix}_id" name="${personPrefix}_id"
                 placeholder="http://orcid.org/0000-0002-1825-0097" />
         </p>
         <p>
         <label for="${personPrefix}_affiliation">Affiliation</label>
             <input type="text" id="${personPrefix}_affiliation" name="${personPrefix}_affiliation"
                 placeholder="Department of Computer Science, University of Pisa" />
         </p>
         <input type="hidden" id="${personPrefix}_role_index" value="0" />
         <input type="button" id="${personPrefix}_role_add" value="Add one role" />
     `;
 
     return fieldset;
 }
 
 function addPersonWithId(container, prefix, legend, id) {
     var personPrefix = `${prefix}_${id}`;
     var fieldset = createPersonFieldset(personPrefix, `${legend} #${id}`);
 
     container.appendChild(fieldset);
 
     document.querySelector(`#${personPrefix}_moveToLeft`)
         .addEventListener('click', () => movePerson(prefix, id, "left"));
     document.querySelector(`#${personPrefix}_moveToRight`)
         .addEventListener('click', () => movePerson(prefix, id, "right"));
     document.querySelector(`#${personPrefix}_role_add`)
         .addEventListener('click', () => addRole(personPrefix));
 }
 
 function movePerson(prefix, id1, direction) {
     var nbPersons = getNbPersons(prefix);
     var id2;
 
     // Computer id2, the id of the person to flip id1 with (wraps around the
     // end of the list of persons)
     if (direction == "left") {
         id2 = id1 - 1;
         if (id2 <= 0) {
             id2 = nbPersons;
         }
     }
     else {
         id2 = id1 + 1;
         if (id2 > nbPersons) {
             id2 = 1;
         }
     }
 
     // Flip the field values, one by one
     personFields.forEach((fieldName) => {
         var field1 = document.querySelector(`#${prefix}_${id1}_${fieldName}`);
         var field2 = document.querySelector(`#${prefix}_${id2}_${fieldName}`);
         var value1 = field1.value;
         var value2 = field2.value;
         field2.value = value1;
         field1.value = value2;
     });
 
     // Form was changed; regenerate
     generateCodemeta();
 }
 
 function addPerson(prefix, legend) {
     var container = document.querySelector(`#${prefix}_container`);
     var personId = getNbPersons(prefix) + 1;
 
     addPersonWithId(container, prefix, legend, personId);
 
     setNbPersons(prefix, personId);
 
     return personId;
 }
 
 function removePerson(prefix) {
     var personId = getNbPersons(prefix);
 
     document.querySelector(`#${prefix}_${personId}`).remove();
 
     setNbPersons(prefix, personId - 1);
 }
 
 // Initialize a group of persons (authors, contributors) on page load.
 // Useful if the page is reloaded.
 function initPersons(prefix, legend) {
     var nbPersons = getNbPersons(prefix);
     var personContainer = document.querySelector(`#${prefix}_container`)
 
     for (let personId = 1; personId <= nbPersons; personId++) {
         addPersonWithId(personContainer, prefix, legend, personId);
     }
 }
 
 function removePersons(prefix) {
     var nbPersons = getNbPersons(prefix);
     var personContainer = document.querySelector(`#${prefix}_container`)
 
     for (let personId = 1; personId <= nbPersons; personId++) {
         removePerson(prefix)
     }
 }
 
 function addRole(personPrefix) {
     const roleButtonGroup = document.querySelector(`#${personPrefix}_role_add`);
     const roleIndexNode = document.querySelector(`#${personPrefix}_role_index`);
     const roleIndex = parseInt(roleIndexNode.value, 10);
 
     const ul = document.createElement("ul")
     ul.classList.add("role");
     ul.id = `${personPrefix}_role_${roleIndex}`;
 
     ul.innerHTML = `
         <li><label for="${personPrefix}_roleName_${roleIndex}">Role</label>
             <input type="text" class="roleName" id="${personPrefix}_roleName_${roleIndex}" name="${personPrefix}_roleName_${roleIndex}"
                 placeholder="Developer" size="10" /></li>
         <li><label for="${personPrefix}_startDate_${roleIndex}">Start date:</label>
             <input type="date" class="startDate" id="${personPrefix}_startDate_${roleIndex}" name="${personPrefix}_startDate_${roleIndex}" /></li>
         <li><label for="${personPrefix}_endDate_${roleIndex}">End date:</label>
             <input type="date" class="endDate" id="${personPrefix}_endDate_${roleIndex}" name="${personPrefix}_endDate_${roleIndex}" /></li>
         <li><input type="button" id="${personPrefix}_role_remove_${roleIndex}" value="X" title="Remove role" /></li>
     `;
     roleButtonGroup.after(ul);
 
     document.querySelector(`#${personPrefix}_role_remove_${roleIndex}`)
         .addEventListener('click', () => removeRole(personPrefix, roleIndex));
 
     roleIndexNode.value = roleIndex + 1;
+
+    return roleIndex;
 }
 
 function removeRole(personPrefix, roleIndex) {
     document.querySelector(`#${personPrefix}_role_${roleIndex}`).remove();
 }
 
 function resetForm() {
     removePersons('author');
     removePersons('contributor');
     // Reset the list of selected licenses
     document.getElementById("selected-licenses").innerHTML = '';
 
     // Reset the form after deleting elements, so nbPersons doesn't get
     // reset before it's read.
     document.querySelector('#inputForm').reset();
 }
 
 function fieldToLower(event) {
     event.target.value = event.target.value.toLowerCase();
 }
 
 function initCallbacks() {
     document.querySelector('#license')
         .addEventListener('change', validateLicense);
 
     document.querySelector('#generateCodemetaV2').disabled = false;
     document.querySelector('#generateCodemetaV2')
         .addEventListener('click', () => generateCodemeta("2.0"));
 
     document.querySelector('#generateCodemetaV3').disabled = false;
     document.querySelector('#generateCodemetaV3')
         .addEventListener('click', () => generateCodemeta("3.0"));
 
     document.querySelector('#resetForm')
         .addEventListener('click', resetForm);
 
     document.querySelector('#validateCodemeta').disabled = false;
     document.querySelector('#validateCodemeta')
         .addEventListener('click', () => parseAndValidateCodemeta(true));
 
     document.querySelector('#importCodemeta').disabled = false;
     document.querySelector('#importCodemeta')
         .addEventListener('click', importCodemeta);
 
     document.querySelector('#inputForm')
         .addEventListener('change', () => generateCodemeta());
 
     document.querySelector('#developmentStatus')
         .addEventListener('change', fieldToLower);
 
     initPersons('author', 'Author');
     initPersons('contributor', 'Contributor');
 }
diff --git a/js/validation/index.js b/js/validation/index.js
index c2ca222..4035f8c 100644
--- a/js/validation/index.js
+++ b/js/validation/index.js
@@ -1,94 +1,96 @@
 /**
  * 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
  */
 
 /*
  * Reads a Codemeta file and shows human-friendly errors on it.
  *
  * This validator intentionaly does not use a schema, in order to show errors
  * that are easy to understand for users with no understanding of JSON-LD.
  */
 
 
 function validateDocument(doc) {
     if (!Array.isArray(doc) && typeof doc != 'object') {
         setError("Document must be an object (starting and ending with { and }), not ${typeof doc}.")
         return false;
     }
     // TODO: validate id/@id
 
     // TODO: check there is either type or @type but not both
     var type = getDocumentType(doc);
     if (type === undefined) {
         setError("Missing type (must be SoftwareSourceCode or SoftwareApplication).")
         return false;
     }
     else if (!isCompactTypeEqual(type, "SoftwareSourceCode") && !isCompactTypeEqual(type, "SoftwareApplication")) {
         // Check this before other fields, as a wrong type error is more
         // understandable than "invalid field".
         setError(`Wrong document type: must be "SoftwareSourceCode"/"SoftwareApplication", not ${JSON.stringify(type)}`)
         return false;
     }
     else {
         return Object.entries(doc).every((entry) => {
             var fieldName = entry[0];
             var subdoc = entry[1];
             if (fieldName == "@context") {
                 // Was checked before
                 return true;
             }
             else if (fieldName == "type" || fieldName == "@type") {
                 // Was checked before
                 return true;
             }
             else if (isFieldFromOtherVersionToIgnore(fieldName)) {
                 // Do not check fields from other versions FIXME
                 return true;
             }
             else {
                 var validator = softwareFieldValidators[fieldName];
                 if (validator === undefined) {
                     // TODO: find if it's a field that belongs to another type,
                     // and suggest that to the user
                     setError(`Unknown field "${fieldName}".`)
                     return false;
                 }
                 else {
                     return validator(fieldName, subdoc);
                 }
             }
         });
     }
 }
 
 
 async function parseAndValidateCodemeta(showPopup) {
     var codemetaText = document.querySelector('#codemetaText').innerText;
     let parsed, doc;
 
     try {
         parsed = JSON.parse(codemetaText);
     }
     catch (e) {
         setError(`Could not read codemeta document because it is not valid JSON (${e}). Check for missing or extra quote, colon, or bracket characters.`);
         return;
     }
 
     setError("");
 
     var isValid = validateDocument(parsed);
     if (showPopup) {
         if (isValid) {
             alert('Document is valid!')
         }
         else {
             alert('Document is invalid.');
         }
     }
 
-    doc = await jsonld.compact(parsed, CODEMETA_CONTEXTS["2.0"].url); // Only import codemeta v2.0 for now
+    parsed["@context"] = LOCAL_CONTEXT_URL;
+    const expanded = await jsonld.expand(parsed);
+    doc = await jsonld.compact(expanded, LOCAL_CONTEXT_URL);
     return doc;
 }