From 3d367b0589fba1aeca7d30d5d30057a319fd857f Mon Sep 17 00:00:00 2001 From: Aldo Mihasi Date: Wed, 21 Jun 2023 12:00:20 +0300 Subject: [PATCH] #8765 - make pids coming from apis as hypelinks in export --- .../managers/DataManagementPlanManager.java | 2 +- .../eudat/logic/managers/DatasetManager.java | 4 +- .../config/configloaders/ConfigLoader.java | 2 + .../configloaders/DefaultConfigLoader.java | 29 +++ .../utilities/documents/word/WordBuilder.java | 206 +++++++++++++----- .../eu/eudat/models/data/pid/PidLink.java | 21 ++ .../eu/eudat/models/data/pid/PidLinks.java | 16 ++ .../config/application-devel.properties | 1 + .../config/application-docker.properties | 1 + .../config/application-production.properties | 1 + .../resources/config/application.properties | 1 + .../web/src/main/resources/pidLinks.json | 100 +++++++++ 12 files changed, 324 insertions(+), 60 deletions(-) create mode 100644 dmp-backend/web/src/main/java/eu/eudat/models/data/pid/PidLink.java create mode 100644 dmp-backend/web/src/main/java/eu/eudat/models/data/pid/PidLinks.java create mode 100644 dmp-backend/web/src/main/resources/pidLinks.json diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DataManagementPlanManager.java b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DataManagementPlanManager.java index cb2e8a2bc..4e38e6411 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DataManagementPlanManager.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DataManagementPlanManager.java @@ -1192,7 +1192,7 @@ public class DataManagementPlanManager { } public FileEnvelope getWordDocument(String id, Principal principal, ConfigLoader configLoader, Boolean versioned) throws IOException { - WordBuilder wordBuilder = new WordBuilder(this.environment); + WordBuilder wordBuilder = new WordBuilder(this.environment, configLoader); VisibilityRuleService visibilityRuleService = new VisibilityRuleServiceImpl(); DatasetWizardModel dataset = new DatasetWizardModel(); XWPFDocument document = configLoader.getDocument(); diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DatasetManager.java b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DatasetManager.java index 793f6382d..875181cc7 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DatasetManager.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DatasetManager.java @@ -397,7 +397,7 @@ public class DatasetManager { } private XWPFDocument getWordDocument(ConfigLoader configLoader, eu.eudat.data.entities.Dataset datasetEntity, VisibilityRuleService visibilityRuleService, Principal principal) throws IOException { - WordBuilder wordBuilder = new WordBuilder(this.environment); + WordBuilder wordBuilder = new WordBuilder(this.environment, configLoader); DatasetWizardModel dataset = new DatasetWizardModel(); XWPFDocument document = configLoader.getDatasetDocument(); @@ -509,7 +509,7 @@ public class DatasetManager { } private XWPFDocument getLightWordDocument(ConfigLoader configLoader, DatasetWizardModel dataset, VisibilityRuleService visibilityRuleService) throws IOException { - WordBuilder wordBuilder = new WordBuilder(this.environment); + WordBuilder wordBuilder = new WordBuilder(this.environment, configLoader); XWPFDocument document = configLoader.getDocument(); // Space below Dataset title. diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/proxy/config/configloaders/ConfigLoader.java b/dmp-backend/web/src/main/java/eu/eudat/logic/proxy/config/configloaders/ConfigLoader.java index f253f5c12..ff27f43fe 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/proxy/config/configloaders/ConfigLoader.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/proxy/config/configloaders/ConfigLoader.java @@ -3,6 +3,7 @@ package eu.eudat.logic.proxy.config.configloaders; import eu.eudat.logic.proxy.config.ExternalUrls; import eu.eudat.logic.proxy.config.Semantic; import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.ConfigurableProviders; +import eu.eudat.models.data.pid.PidLinks; import org.apache.poi.xwpf.usermodel.XWPFDocument; import java.util.List; @@ -14,5 +15,6 @@ public interface ConfigLoader { XWPFDocument getDocument(); XWPFDocument getDatasetDocument(); ConfigurableProviders getConfigurableProviders(); + PidLinks getPidLinks(); Map getKeyToSourceMap(); } diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/proxy/config/configloaders/DefaultConfigLoader.java b/dmp-backend/web/src/main/java/eu/eudat/logic/proxy/config/configloaders/DefaultConfigLoader.java index 4faa489d9..b9f7ddb0c 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/proxy/config/configloaders/DefaultConfigLoader.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/proxy/config/configloaders/DefaultConfigLoader.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import eu.eudat.logic.proxy.config.ExternalUrls; import eu.eudat.logic.proxy.config.Semantic; import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.ConfigurableProviders; +import eu.eudat.models.data.pid.PidLinks; import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,6 +42,7 @@ public class DefaultConfigLoader implements ConfigLoader { private XWPFDocument document; private XWPFDocument datasetDocument; private ConfigurableProviders configurableProviders; + private PidLinks pidLinks; private Map keyToSourceMap; @Autowired @@ -134,6 +136,25 @@ public class DefaultConfigLoader implements ConfigLoader { } } + private void setPidLinks() { + String filePath = environment.getProperty("configuration.pid_links"); + logger.info("Loaded also config file: " + filePath); + InputStream is = null; + try { + is = getStreamFromPath(filePath); + ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + this.pidLinks = mapper.readValue(is, PidLinks.class); + } catch (IOException | NullPointerException e) { + logger.error(e.getMessage(), e); + } finally { + try { + if (is != null) is.close(); + } catch (IOException e) { + logger.warn("Warning: Could not close a stream after reading from file: " + filePath, e); + } + } + } + private void setKeyToSourceMap() { String filePath = this.environment.getProperty("configuration.externalUrls"); logger.info("Loaded also config file: " + filePath); @@ -192,6 +213,14 @@ public class DefaultConfigLoader implements ConfigLoader { return configurableProviders; } + public PidLinks getPidLinks() { + if (pidLinks == null) { + pidLinks = new PidLinks(); + this.setPidLinks(); + } + return pidLinks; + } + public Map getKeyToSourceMap() { if (keyToSourceMap == null) { keyToSourceMap = new HashMap<>(); diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/utilities/documents/word/WordBuilder.java b/dmp-backend/web/src/main/java/eu/eudat/logic/utilities/documents/word/WordBuilder.java index 0a3d64dbe..a10cf44d8 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/utilities/documents/word/WordBuilder.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/utilities/documents/word/WordBuilder.java @@ -7,11 +7,14 @@ import eu.eudat.data.entities.DMP; import eu.eudat.data.entities.Dataset; import eu.eudat.data.entities.Organisation; import eu.eudat.data.entities.Researcher; +import eu.eudat.logic.proxy.config.configloaders.ConfigLoader; import eu.eudat.logic.services.forms.VisibilityRuleService; import eu.eudat.logic.utilities.documents.types.ParagraphStyle; import eu.eudat.logic.utilities.interfaces.ApplierWithValue; import eu.eudat.logic.utilities.json.JavaToJson; import eu.eudat.models.data.components.commons.datafield.*; +import eu.eudat.models.data.pid.PidLink; +import eu.eudat.models.data.pid.PidLinks; import eu.eudat.models.data.user.components.datasetprofile.Field; import eu.eudat.models.data.user.components.datasetprofile.FieldSet; import eu.eudat.models.data.user.components.datasetprofile.Section; @@ -71,8 +74,9 @@ public class WordBuilder { private Integer indent; private final ObjectMapper mapper; private Integer imageCount; + private ConfigLoader configLoader; - public WordBuilder(Environment environment) { + public WordBuilder(Environment environment, ConfigLoader configLoader) { this.cTAbstractNum = CTAbstractNum.Factory.newInstance(); this.cTAbstractNum.setAbstractNumId(BigInteger.valueOf(1)); this.indent = 0; @@ -80,6 +84,7 @@ public class WordBuilder { this.mapper = new ObjectMapper(); this.buildOptions(environment); this.buildOptionsInTable(environment); + this.configLoader = configLoader; } private void buildOptionsInTable(Environment environment) { @@ -568,6 +573,37 @@ public class WordBuilder { return hasValue; } + private void createHypeLink(XWPFDocument mainDocumentPart, String format, String pidType, String pid, boolean hasMultiplicityItems, boolean isMultiAutoComplete){ + PidLink pidLink = this.configLoader.getPidLinks().getPidLinks().stream().filter(pl -> pl.getPid().equals(pidType)).findFirst().orElse(null); + if (pidLink != null) { + if (!hasMultiplicityItems) { + XWPFParagraph paragraph = mainDocumentPart.createParagraph(); + paragraph.setIndentFromLeft(400 * indent); + if (numId != null) { + paragraph.setNumID(numId); + } + } + if (isMultiAutoComplete) { + XWPFRun r = mainDocumentPart.getLastParagraph().createRun(); + r.setText("• "); + } + XWPFHyperlinkRun run = mainDocumentPart.getLastParagraph().createHyperlinkRun(pidLink.getLink().replace("{pid}", pid)); + run.setText(format); + run.setUnderline(UnderlinePatterns.SINGLE); + run.setColor("0000FF"); + run.setFontSize(11); + } + else { + String newFormat = (isMultiAutoComplete) ? "• " + format : format; + if (hasMultiplicityItems) { + mainDocumentPart.getLastParagraph().createRun().setText(newFormat); + } + else { + addParagraphContent(newFormat, mainDocumentPart, ParagraphStyle.TEXT, numId, indent); + } + } + } + private Boolean createFields(List fields, XWPFDocument mainDocumentPart, Integer indent, Boolean createListing, VisibilityRuleService visibilityRuleService, boolean hasMultiplicityItems) { if (createListing) this.addListing(mainDocumentPart, indent, false, false); boolean hasValue = false; @@ -607,70 +643,126 @@ public class WordBuilder { } else if (field.getViewStyle().getRenderStyle().equals("combobox") && field.getData() instanceof AutoCompleteData) { format = getCommaSeparatedFormatsFromJson(format, "label"); } - boolean isResearcher = field.getViewStyle().getRenderStyle().equals("researchers"); - if(format != null && !format.isEmpty()){ - Object hasMultiAutoComplete = mapper.convertValue(field.getData(), Map.class).get("multiAutoComplete"); - boolean isMultiAutoComplete = hasMultiAutoComplete != null && (boolean)hasMultiAutoComplete; - boolean arrayStringFormat = format.charAt(0) == '['; - if(arrayStringFormat || isMultiAutoComplete){ - List values = (arrayStringFormat) ? Arrays.asList(format.substring(1, format.length() - 1).split(",[ ]*")) : Arrays.asList(format.split(",[ ]*")); - if(values.size() > 1) { - boolean orcidResearcher; - for (String val : values) { - orcidResearcher = false; - String orcId = null; - if(isResearcher && val.contains("orcid:")){ - orcId = val.substring(val.indexOf(':') + 1, val.indexOf(')')); - val = val.substring(0, val.indexOf(':') + 1) + " "; - orcidResearcher = true; - } - format = "• " + val; + switch (field.getViewStyle().getRenderStyle()) { + case "organizations": + case "externalDatasets": + case "publications": + if(format != null && !format.isEmpty()){ + Object hasMultiAutoComplete = mapper.convertValue(field.getData(), Map.class).get("multiAutoComplete"); + boolean isMultiAutoComplete = hasMultiAutoComplete != null && (boolean)hasMultiAutoComplete; + if(!isMultiAutoComplete){ + Map value = mapper.readValue((String)field.getValue(), Map.class); if(hasMultiplicityItems){ - mainDocumentPart.getLastParagraph().createRun().setText(format); - if(orcidResearcher){ - XWPFHyperlinkRun run = mainDocumentPart.getLastParagraph().createHyperlinkRun("https://orcid.org/" + orcId); - run.setText(orcId); - run.setUnderline(UnderlinePatterns.SINGLE); - run.setColor("0000FF"); - mainDocumentPart.getLastParagraph().createRun().setText(")"); - } + createHypeLink(mainDocumentPart, format, value.get("pidTypeField"), value.get("pid"), true, false); hasMultiplicityItems = false; } else{ - XWPFParagraph paragraph = addParagraphContent(format, mainDocumentPart, field.getViewStyle().getRenderStyle().equals("richTextarea") ? ParagraphStyle.HTML : ParagraphStyle.TEXT, numId, indent); - if(orcidResearcher){ - XWPFHyperlinkRun run = paragraph.createHyperlinkRun("https://orcid.org/" + orcId); - run.setText(orcId); - run.setUnderline(UnderlinePatterns.SINGLE); - run.setColor("0000FF"); - paragraph.createRun().setText(")"); - } - if (paragraph != null) { -// CTDecimalNumber number = paragraph.getCTP().getPPr().getNumPr().addNewIlvl(); -// number.setVal(BigInteger.valueOf(indent)); - hasValue = true; + createHypeLink(mainDocumentPart, format, value.get("pidTypeField"), value.get("pid"), false, false); + } + } + else{ + mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); + List> values = new ArrayList<>(); + try { + values = Arrays.asList(mapper.readValue(field.getValue().toString(), HashMap[].class)); + } + catch (Exception e) { + Map map = new HashMap<>(); + map.put("label", field.getValue()); + values.add(map); + } + if (values.size() > 1) { + for (Map value : values) { + if(hasMultiplicityItems){ + createHypeLink(mainDocumentPart, (String) value.get("name"), (String) value.get("pidTypeField"), (String) value.get("pid"), true, true); + hasMultiplicityItems = false; + } + else{ + createHypeLink(mainDocumentPart, (String) value.get("name"), (String) value.get("pidTypeField"), (String) value.get("pid"), false, true); + } } } - format = null; + else if(values.size() == 1){ + if(hasMultiplicityItems){ + createHypeLink(mainDocumentPart, format, (String) values.get(0).get("pidTypeField"), (String) values.get(0).get("pid"), true, false); + hasMultiplicityItems = false; + } + else{ + createHypeLink(mainDocumentPart, format, (String) values.get(0).get("pidTypeField"), (String) values.get(0).get("pid"), false, false); + } + } + } + hasValue = true; + } + break; + default: + boolean isResearcher = field.getViewStyle().getRenderStyle().equals("researchers"); + if(format != null && !format.isEmpty()){ + Object hasMultiAutoComplete = mapper.convertValue(field.getData(), Map.class).get("multiAutoComplete"); + boolean isMultiAutoComplete = hasMultiAutoComplete != null && (boolean)hasMultiAutoComplete; + boolean arrayStringFormat = format.charAt(0) == '['; + if(arrayStringFormat || isMultiAutoComplete){ + List values = (arrayStringFormat) ? Arrays.asList(format.substring(1, format.length() - 1).split(",[ ]*")) : Arrays.asList(format.split(",[ ]*")); + if(values.size() > 1) { + boolean orcidResearcher; + for (String val : values) { + orcidResearcher = false; + String orcId = null; + if(isResearcher && val.contains("orcid:")){ + orcId = val.substring(val.indexOf(':') + 1, val.indexOf(')')); + val = val.substring(0, val.indexOf(':') + 1) + " "; + orcidResearcher = true; + } + format = "• " + val; + if(hasMultiplicityItems){ + mainDocumentPart.getLastParagraph().createRun().setText(format); + if(orcidResearcher){ + XWPFHyperlinkRun run = mainDocumentPart.getLastParagraph().createHyperlinkRun("https://orcid.org/" + orcId); + run.setText(orcId); + run.setUnderline(UnderlinePatterns.SINGLE); + run.setColor("0000FF"); + mainDocumentPart.getLastParagraph().createRun().setText(")"); + } + hasMultiplicityItems = false; + } + else{ + XWPFParagraph paragraph = addParagraphContent(format, mainDocumentPart, field.getViewStyle().getRenderStyle().equals("richTextarea") ? ParagraphStyle.HTML : ParagraphStyle.TEXT, numId, indent); + if(orcidResearcher){ + XWPFHyperlinkRun run = paragraph.createHyperlinkRun("https://orcid.org/" + orcId); + run.setText(orcId); + run.setUnderline(UnderlinePatterns.SINGLE); + run.setColor("0000FF"); + paragraph.createRun().setText(")"); + } + if (paragraph != null) { +// CTDecimalNumber number = paragraph.getCTP().getPPr().getNumPr().addNewIlvl(); +// number.setVal(BigInteger.valueOf(indent)); + hasValue = true; + } + } + format = null; + } + } + else if(values.size() == 1){ + format = values.get(0); + } } } - else if(values.size() == 1){ - format = values.get(0); + if (format != null) { + if (hasMultiplicityItems) { + mainDocumentPart.getLastParagraph().createRun().setText(format); + hasMultiplicityItems = false; + hasValue = true; + } + else { + XWPFParagraph paragraph = addParagraphContent(format, mainDocumentPart, field.getViewStyle().getRenderStyle().equals("richTextarea") ? ParagraphStyle.HTML : ParagraphStyle.TEXT, numId, indent); + if (paragraph != null) { +// CTDecimalNumber number = paragraph.getCTP().getPPr().getNumPr().addNewIlvl(); +// number.setVal(BigInteger.valueOf(indent)); + hasValue = true; + } + } } - } - } - if(hasMultiplicityItems && format != null){ - mainDocumentPart.getLastParagraph().createRun().setText(format); - hasMultiplicityItems = false; - hasValue = true; - } - else{ - XWPFParagraph paragraph = addParagraphContent(format, mainDocumentPart, field.getViewStyle().getRenderStyle().equals("richTextarea") ? ParagraphStyle.HTML : ParagraphStyle.TEXT, numId, indent); - if (paragraph != null) { -// CTDecimalNumber number = paragraph.getCTP().getPPr().getNumPr().addNewIlvl(); -// number.setVal(BigInteger.valueOf(indent)); - hasValue = true; - } } } } catch (IOException e) { @@ -881,7 +973,7 @@ public class WordBuilder { // logger.info("Reverting to custom parsing"); identifierData = customParse(field.getValue().toString()); } - return "id: " + identifierData.get("identifier") + ", Validation Type: " + identifierData.get("type"); + return "id: " + identifierData.get("identifier") + ", Type: " + identifierData.get("type"); } return ""; } diff --git a/dmp-backend/web/src/main/java/eu/eudat/models/data/pid/PidLink.java b/dmp-backend/web/src/main/java/eu/eudat/models/data/pid/PidLink.java new file mode 100644 index 000000000..bb0b83456 --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/models/data/pid/PidLink.java @@ -0,0 +1,21 @@ +package eu.eudat.models.data.pid; + +public class PidLink { + + private String pid; + private String link; + + public String getPid() { + return pid; + } + public void setPid(String pid) { + this.pid = pid; + } + + public String getLink() { + return link; + } + public void setLink(String link) { + this.link = link; + } +} diff --git a/dmp-backend/web/src/main/java/eu/eudat/models/data/pid/PidLinks.java b/dmp-backend/web/src/main/java/eu/eudat/models/data/pid/PidLinks.java new file mode 100644 index 000000000..4d1ffa3af --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/models/data/pid/PidLinks.java @@ -0,0 +1,16 @@ +package eu.eudat.models.data.pid; + +import java.util.ArrayList; +import java.util.List; + +public class PidLinks { + + private List pidLinks = new ArrayList<>(); + + public List getPidLinks() { + return pidLinks; + } + public void setPidLinks(List pidLinks) { + this.pidLinks = pidLinks; + } +} diff --git a/dmp-backend/web/src/main/resources/config/application-devel.properties b/dmp-backend/web/src/main/resources/config/application-devel.properties index 7156a62d5..372b55e17 100644 --- a/dmp-backend/web/src/main/resources/config/application-devel.properties +++ b/dmp-backend/web/src/main/resources/config/application-devel.properties @@ -27,6 +27,7 @@ configuration.semantics=Semantics.json configuration.h2020template=documents/h2020.docx configuration.h2020datasettemplate=documents/h2020_dataset.docx configuration.configurable_login_providers=configurableLoginProviders.json +configuration.pid_links=pidLinks.json ####################EMAIL FILE TEMPLATES OVERRIDES CONFIGURATIONS########## email.invite=classpath:templates/email/email.html diff --git a/dmp-backend/web/src/main/resources/config/application-docker.properties b/dmp-backend/web/src/main/resources/config/application-docker.properties index caeec702f..2c200ae68 100644 --- a/dmp-backend/web/src/main/resources/config/application-docker.properties +++ b/dmp-backend/web/src/main/resources/config/application-docker.properties @@ -26,6 +26,7 @@ configuration.externalUrls=externalUrls/ExternalUrls.xml configuration.h2020template=documents/h2020.docx configuration.h2020datasettemplate=documents/h2020_dataset.docx configuration.configurable_login_providers=configurableLoginProviders.json +configuration.pid_links=pidLinks.json ####################EMAIL FILE TEMPLATES OVERRIDES CONFIGURATIONS########## email.invite=classpath:templates/email/email.html diff --git a/dmp-backend/web/src/main/resources/config/application-production.properties b/dmp-backend/web/src/main/resources/config/application-production.properties index 61118ce55..87b5847bc 100644 --- a/dmp-backend/web/src/main/resources/config/application-production.properties +++ b/dmp-backend/web/src/main/resources/config/application-production.properties @@ -21,6 +21,7 @@ configuration.h2020template=documents/h2020.docx configuration.h2020datasettemplate=documents/h2020_dataset.docx configuration.configurable_login_providers=ConfigurableLoginProviders.json configuration.doi_funder=DOI_Funder.json +configuration.pid_links=pidLinks.json ####################SPRING MAIL CONFIGURATIONS################# spring.mail.default-encoding=UTF-8 diff --git a/dmp-backend/web/src/main/resources/config/application.properties b/dmp-backend/web/src/main/resources/config/application.properties index 0135ef7d6..0facad0ba 100644 --- a/dmp-backend/web/src/main/resources/config/application.properties +++ b/dmp-backend/web/src/main/resources/config/application.properties @@ -53,6 +53,7 @@ configuration.semantics=Semantics.json configuration.h2020template=documents/h2020.docx configuration.h2020datasettemplate=documents/h2020_dataset.docx configuration.configurable_login_providers=configurableLoginProviders.json +configuration.pid_links=pidLinks.json ####################EMAIL FILE TEMPLATES OVERRIDES CONFIGURATIONS########## email.invite=file:templates/email/email.html diff --git a/dmp-backend/web/src/main/resources/pidLinks.json b/dmp-backend/web/src/main/resources/pidLinks.json new file mode 100644 index 000000000..d2a011677 --- /dev/null +++ b/dmp-backend/web/src/main/resources/pidLinks.json @@ -0,0 +1,100 @@ +{ + "pidLinks": [ + { + "pid": "doi", + "link": "https://doi.org/{pid}" + }, + { + "pid": "uniprot", + "link": "https://uniprot.org/uniprotkb/{pid}" + }, + { + "pid": "handle", + "link": "https://hdl.handle.net/{pid}" + }, + { + "pid": "arxiv", + "link": "https://arxiv.org/abs/{pid}" + }, + { + "pid": "ascl", + "link": "https://ascl.net/{pid}" + }, + { + "pid": "orcid", + "link": "https://orcid.org/{pid}" + }, + { + "pid": "pmid", + "link": "https://pubmed.ncbi.nlm.nih.gov/{pid}" + }, + { + "pid": "ads", + "link": "https://ui.adsabs.harvard.edu/#abs/{pid}" + }, + { + "pid": "pmcid", + "link": "https://ncbi.nlm.nih.gov/pmc/{pid}" + }, + { + "pid": "gnd", + "link": "https://d-nb.info/gnd/{pid}" + }, + { + "pid": "urn", + "link": "https://nbn-resolving.org/{pid}" + }, + { + "pid": "sra", + "link": "https://ebi.ac.uk/ena/data/view/{pid}" + }, + { + "pid": "bioproject", + "link": "https://ebi.ac.uk/ena/data/view/{pid}" + }, + { + "pid": "biosample", + "link": "https://ebi.ac.uk/ena/data/view/{pid}" + }, + { + "pid": "ensembl", + "link": "https://ensembl.org/id/{pid}" + }, + { + "pid": "refseq", + "link": "https://ncbi.nlm.nih.gov/entrez/viewer.fcgi?val={pid}" + }, + { + "pid": "genome", + "link": "https://ncbi.nlm.nih.gov/assembly/{pid}" + }, + { + "pid": "geo", + "link": "https://ncbi.nlm.nih.gov/geo/query/acc.cgi?acc={pid}" + }, + { + "pid": "arrayexpress_array", + "link": "https://ebi.ac.uk/arrayexpress/arrays/{pid}" + }, + { + "pid": "arrayexpress_experiment", + "link": "https://ebi.ac.uk/arrayexpress/experiments/{pid}" + }, + { + "pid": "hal", + "link": "https://hal.archives-ouvertes.fr/{pid}" + }, + { + "pid": "swh", + "link": "https://archive.softwareheritage.org/{pid}" + }, + { + "pid": "ror", + "link": "https://ror.org/{pid}" + }, + { + "pid": "viaf", + "link": "https://viaf.org/viaf/{pid}" + } + ] +} \ No newline at end of file