[VIVO-1415] Add publication claiming from PubMed and CrossRef (#129)
* Claiming interface for DOI and PMID * Allow http://doi.org urls in the claim process * Make button i18n compliant * Check Editor already claimed, error for no citation, PUMCIDs fixed * Fixed gnarly DOI containing semi-colon (removed semi-colon as separator), match more DOI formats internally, improve AUTH restrictions * Add Wilma themes * Add permissions to allow proxy editors * Update DOI resolver URI for content negotiation * CSS changes to prevent layout problems in admin pages * Add selectable list of publication types on import Resolves to https://jira.duraspace.org/browse/VIVO-1415
This commit is contained in:
parent
0a08c2890d
commit
f597b7ee42
31 changed files with 4197 additions and 9 deletions
|
@ -2,6 +2,7 @@ package edu.cornell.mannlib.vitro.webapp.controller.individual;
|
||||||
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||||
|
import org.vivoweb.webapp.controller.freemarker.CreateAndLinkResourceController;
|
||||||
|
|
||||||
import javax.servlet.ServletContextEvent;
|
import javax.servlet.ServletContextEvent;
|
||||||
import javax.servlet.ServletContextListener;
|
import javax.servlet.ServletContextListener;
|
||||||
|
@ -24,6 +25,13 @@ public class VIVOIndividualResponseBuilderExtension implements IndividualRespons
|
||||||
public void addOptions(VitroRequest vreq, Map<String, Object> body) {
|
public void addOptions(VitroRequest vreq, Map<String, Object> body) {
|
||||||
addAltMetricOptions(vreq, body);
|
addAltMetricOptions(vreq, body);
|
||||||
addPlumPrintOptions(vreq, body);
|
addPlumPrintOptions(vreq, body);
|
||||||
|
addEnabledClaimingSources(vreq, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addEnabledClaimingSources(VitroRequest vreq, Map<String, Object> body) {
|
||||||
|
ConfigurationProperties props = ConfigurationProperties.getBean(vreq);
|
||||||
|
body.put("claimSources", CreateAndLinkResourceController.getEnabledProviders(props));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAltMetricOptions(VitroRequest vreq, Map<String, Object> body) {
|
private void addAltMetricOptions(VitroRequest vreq, Map<String, Object> body) {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,79 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package org.vivoweb.webapp.createandlink;
|
||||||
|
|
||||||
|
public class Citation {
|
||||||
|
public String externalId;
|
||||||
|
public String externalProvider;
|
||||||
|
public String externalResource;
|
||||||
|
|
||||||
|
public String vivoUri;
|
||||||
|
|
||||||
|
public String type;
|
||||||
|
public String typeUri;
|
||||||
|
public String title;
|
||||||
|
public Name[] authors;
|
||||||
|
public String journal;
|
||||||
|
public String volume;
|
||||||
|
public String issue;
|
||||||
|
public String pagination;
|
||||||
|
public Integer publicationYear;
|
||||||
|
public String DOI;
|
||||||
|
|
||||||
|
public boolean alreadyClaimed = false;
|
||||||
|
public boolean showError = false;
|
||||||
|
|
||||||
|
public String getExternalId() { return externalId; }
|
||||||
|
public String getExternalProvider() { return externalProvider; }
|
||||||
|
public String getExternalResource() { return externalResource; }
|
||||||
|
|
||||||
|
public String getVivoUri() { return vivoUri; }
|
||||||
|
|
||||||
|
public String getType() { return type; }
|
||||||
|
public String getTypeUri() { return typeUri; }
|
||||||
|
public String getTitle() { return title; }
|
||||||
|
public Name[] getAuthors() {
|
||||||
|
return authors;
|
||||||
|
}
|
||||||
|
public String getJournal() {
|
||||||
|
return journal;
|
||||||
|
}
|
||||||
|
public String getVolume() {
|
||||||
|
return volume;
|
||||||
|
}
|
||||||
|
public String getIssue() {
|
||||||
|
return issue;
|
||||||
|
}
|
||||||
|
public String getPagination() {
|
||||||
|
return pagination;
|
||||||
|
}
|
||||||
|
public Integer getPublicationYear() {
|
||||||
|
return publicationYear;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDOI() {
|
||||||
|
return DOI;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getAlreadyClaimed() { return alreadyClaimed; }
|
||||||
|
|
||||||
|
public boolean getShowError() { return showError; }
|
||||||
|
|
||||||
|
public static class Name {
|
||||||
|
public String name;
|
||||||
|
|
||||||
|
public boolean linked = false;
|
||||||
|
public boolean proposed = false;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getLinked() {
|
||||||
|
return linked;
|
||||||
|
}
|
||||||
|
public boolean getProposed() {
|
||||||
|
return proposed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package org.vivoweb.webapp.createandlink;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
@JsonIgnoreProperties
|
||||||
|
public class CiteprocJSONModel {
|
||||||
|
public String type;
|
||||||
|
public String id; // Number?
|
||||||
|
public String[] categories;
|
||||||
|
public String language;
|
||||||
|
public String journalAbbreviation;
|
||||||
|
public String shortTitle;
|
||||||
|
public NameField[] author;
|
||||||
|
@JsonProperty("collection-editor")
|
||||||
|
public NameField[] collectionEditor;
|
||||||
|
public NameField[] composer;
|
||||||
|
@JsonProperty("container-author")
|
||||||
|
public NameField[] containerAuthor;
|
||||||
|
public NameField[] director;
|
||||||
|
public NameField[] editor;
|
||||||
|
@JsonProperty("editorial-director")
|
||||||
|
public NameField[] editorialDirector;
|
||||||
|
public NameField[] interviewer;
|
||||||
|
public NameField[] illustrator;
|
||||||
|
@JsonProperty("original-author")
|
||||||
|
public NameField[] originalAuthor;
|
||||||
|
public NameField[] recipient;
|
||||||
|
@JsonProperty("reviewed-author")
|
||||||
|
public NameField[] reviewedAuthor;
|
||||||
|
public NameField[] translator;
|
||||||
|
public DateField accessed;
|
||||||
|
public DateField container;
|
||||||
|
@JsonProperty("event-date")
|
||||||
|
public DateField eventDate;
|
||||||
|
public DateField issued;
|
||||||
|
@JsonProperty("original-date")
|
||||||
|
public DateField originalDate;
|
||||||
|
public DateField submitted;
|
||||||
|
@JsonProperty("abstract")
|
||||||
|
public String abstractText;
|
||||||
|
public String annote;
|
||||||
|
public String archive;
|
||||||
|
public String archive_location;
|
||||||
|
public String authority;
|
||||||
|
@JsonProperty("call-number")
|
||||||
|
public String callNumber;
|
||||||
|
@JsonProperty("chapter-number")
|
||||||
|
public String chapterNumber;
|
||||||
|
@JsonProperty("citation-number")
|
||||||
|
public String citationNumber;
|
||||||
|
@JsonProperty("citation-label")
|
||||||
|
public String citationLabel;
|
||||||
|
@JsonProperty("collection-number")
|
||||||
|
public String collectionNumber;
|
||||||
|
@JsonProperty("container-title")
|
||||||
|
public String containerTitle;
|
||||||
|
@JsonProperty("container-title-short")
|
||||||
|
public String containerTitleShort;
|
||||||
|
public String dimensions;
|
||||||
|
public String DOI;
|
||||||
|
public String edition; // Integer?
|
||||||
|
public String event;
|
||||||
|
@JsonProperty("event-place")
|
||||||
|
public String eventPlace;
|
||||||
|
@JsonProperty("first-reference-note-number")
|
||||||
|
public String firstReferenceNoteNumber;
|
||||||
|
public String genre;
|
||||||
|
public String ISBN;
|
||||||
|
public String ISSN;
|
||||||
|
public String issue; // Integer?
|
||||||
|
public String jurisdiction;
|
||||||
|
public String keyword;
|
||||||
|
public String locator;
|
||||||
|
public String medium;
|
||||||
|
public String note;
|
||||||
|
public String number; // Integer?
|
||||||
|
@JsonProperty("number-of-pages")
|
||||||
|
public String numberOfPages;
|
||||||
|
@JsonProperty("number-of-volumes")
|
||||||
|
public String numberOfVolumes; // Integer?
|
||||||
|
@JsonProperty("original-publisher")
|
||||||
|
public String originalPublisher;
|
||||||
|
@JsonProperty("original-publisher-place")
|
||||||
|
public String originalPublisherPlace;
|
||||||
|
@JsonProperty("original-title")
|
||||||
|
public String originalTitle;
|
||||||
|
public String page;
|
||||||
|
@JsonProperty("page-first")
|
||||||
|
public String pageFirst;
|
||||||
|
public String PMCID;
|
||||||
|
public String PMID;
|
||||||
|
public String publisher;
|
||||||
|
@JsonProperty("publisher-place")
|
||||||
|
public String publisherPlace;
|
||||||
|
public String references;
|
||||||
|
@JsonProperty("reviewed-title")
|
||||||
|
public String reviewedTitle;
|
||||||
|
public String scale;
|
||||||
|
public String section;
|
||||||
|
public String source;
|
||||||
|
public String status;
|
||||||
|
public String title;
|
||||||
|
@JsonProperty("title-short")
|
||||||
|
public String titleShort;
|
||||||
|
public String URL;
|
||||||
|
public String version;
|
||||||
|
public String volume; // Integer?
|
||||||
|
@JsonProperty("year-suffix")
|
||||||
|
public String yearSuffix;
|
||||||
|
|
||||||
|
public static class NameField {
|
||||||
|
public String family;
|
||||||
|
public String given;
|
||||||
|
@JsonProperty("dropping-particle")
|
||||||
|
public String droppingParticle;
|
||||||
|
@JsonProperty("non-dropping-particle")
|
||||||
|
public String nonDroppingParticle;
|
||||||
|
public String suffix;
|
||||||
|
@JsonProperty("comma-suffix")
|
||||||
|
public String commaSuffix; // Number? Boolean?
|
||||||
|
@JsonProperty("staticOrdering")
|
||||||
|
public String staticOrdering; // Number? Boolean?
|
||||||
|
public String literal;
|
||||||
|
@JsonProperty("parse-names")
|
||||||
|
public String parseNames; // Number? Boolean?
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DateField {
|
||||||
|
@JsonProperty("date-parts")
|
||||||
|
public String[][] dateParts; // Number?
|
||||||
|
public String season; // Number?
|
||||||
|
public String circa; // Number? Boolean?
|
||||||
|
public String literal;
|
||||||
|
public String raw;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package org.vivoweb.webapp.createandlink;
|
||||||
|
|
||||||
|
public class ContributorRole {
|
||||||
|
private String key;
|
||||||
|
private String label;
|
||||||
|
private String uri;
|
||||||
|
|
||||||
|
public ContributorRole(String key, String label, String uri) {
|
||||||
|
this.key = key;
|
||||||
|
this.label = label;
|
||||||
|
this.uri = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() { return key; }
|
||||||
|
public String getLabel() { return label; }
|
||||||
|
public String getUri() { return uri; }
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package org.vivoweb.webapp.createandlink;
|
||||||
|
|
||||||
|
public interface CreateAndLinkResourceProvider {
|
||||||
|
String normalize(String id);
|
||||||
|
|
||||||
|
String getLabel();
|
||||||
|
|
||||||
|
ExternalIdentifiers allExternalIDsForFind(String externalId);
|
||||||
|
|
||||||
|
String findInExternal(String id, Citation citation);
|
||||||
|
|
||||||
|
ResourceModel makeResourceModel(String externalId, String externalResource);
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package org.vivoweb.webapp.createandlink;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
public class CreateAndLinkUtils {
|
||||||
|
public static String formatAuthorString(String familyName, String givenName) {
|
||||||
|
if (StringUtils.isEmpty(familyName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder authorBuilder = new StringBuilder(familyName);
|
||||||
|
|
||||||
|
if (!StringUtils.isEmpty(givenName)) {
|
||||||
|
authorBuilder.append(", ");
|
||||||
|
boolean addToAuthor = true;
|
||||||
|
for (char ch : givenName.toCharArray()) {
|
||||||
|
if (addToAuthor) {
|
||||||
|
if (Character.isAlphabetic(ch)) {
|
||||||
|
authorBuilder.append(Character.toUpperCase(ch));
|
||||||
|
addToAuthor = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!Character.isAlphabetic(ch)) {
|
||||||
|
addToAuthor = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorBuilder.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package org.vivoweb.webapp.createandlink;
|
||||||
|
|
||||||
|
public class ExternalIdentifiers {
|
||||||
|
public String DOI;
|
||||||
|
public String PubMedID; // http://www.ncbi.nlm.nih.gov/pmc/utils/idconv/v1.0/?ids=23193287&format=json
|
||||||
|
public String PubMedCentralID; // http://www.ncbi.nlm.nih.gov/pmc/utils/idconv/v1.0/?ids=PMC3531190&format=json
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package org.vivoweb.webapp.createandlink;
|
||||||
|
|
||||||
|
public class ResourceModel {
|
||||||
|
public String DOI;
|
||||||
|
public String PubMedID;
|
||||||
|
public String PubMedCentralID;
|
||||||
|
public String[] ISSN;
|
||||||
|
public String[] ISBN;
|
||||||
|
public String URL;
|
||||||
|
|
||||||
|
public NameField[] author;
|
||||||
|
public NameField[] editor;
|
||||||
|
public NameField[] translator;
|
||||||
|
|
||||||
|
public String containerTitle;
|
||||||
|
public String issue;
|
||||||
|
public String pageStart;
|
||||||
|
public String pageEnd;
|
||||||
|
|
||||||
|
public DateField publicationDate;
|
||||||
|
|
||||||
|
public String publisher;
|
||||||
|
|
||||||
|
public String[] subject;
|
||||||
|
public String title;
|
||||||
|
public String type;
|
||||||
|
public String volume;
|
||||||
|
|
||||||
|
public String status;
|
||||||
|
public String presentedAt;
|
||||||
|
public String[] keyword;
|
||||||
|
public String abstractText;
|
||||||
|
|
||||||
|
public static class NameField {
|
||||||
|
public String family;
|
||||||
|
public String given;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DateField {
|
||||||
|
public Integer year;
|
||||||
|
public Integer month;
|
||||||
|
public Integer day;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,191 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package org.vivoweb.webapp.createandlink.crossref;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import org.vivoweb.webapp.createandlink.utils.StringArrayDeserializer;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note that ISSN and ISBN are arrays in Crossref, whereas Citeproc defines them to be a single value.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties
|
||||||
|
public class CrossrefCiteprocJSONModel {
|
||||||
|
// Crossref Specific Fields
|
||||||
|
|
||||||
|
@JsonDeserialize(using = StringArrayDeserializer.class)
|
||||||
|
public String[] ISSN;
|
||||||
|
@JsonDeserialize(using = StringArrayDeserializer.class)
|
||||||
|
public String[] ISBN;
|
||||||
|
|
||||||
|
public DateField created;
|
||||||
|
// public DateField deposited;
|
||||||
|
// public DateField indexed;
|
||||||
|
|
||||||
|
// public String member;
|
||||||
|
public String prefix;
|
||||||
|
|
||||||
|
@JsonProperty("article-number")
|
||||||
|
public String articleNumber;
|
||||||
|
|
||||||
|
@JsonProperty("published-online")
|
||||||
|
public DateField publishedOnline;
|
||||||
|
|
||||||
|
@JsonProperty("published-print")
|
||||||
|
public DateField publishedPrint;
|
||||||
|
|
||||||
|
// @JsonProperty("reference-count")
|
||||||
|
// public Integer referenceCount;
|
||||||
|
public Double score;
|
||||||
|
@JsonDeserialize(using = StringArrayDeserializer.class)
|
||||||
|
public String[] subject;
|
||||||
|
// public String[] subtitle;
|
||||||
|
|
||||||
|
// Standard Citeproc fields
|
||||||
|
|
||||||
|
public String type;
|
||||||
|
public String id; // Number?
|
||||||
|
// public String[] categories;
|
||||||
|
public String language;
|
||||||
|
// public String journalAbbreviation;
|
||||||
|
// public String shortTitle;
|
||||||
|
public NameField[] author;
|
||||||
|
// @JsonProperty("collection-editor")
|
||||||
|
// public NameField[] collectionEditor;
|
||||||
|
// public NameField[] composer;
|
||||||
|
// @JsonProperty("container-author")
|
||||||
|
// public NameField[] containerAuthor;
|
||||||
|
// public NameField[] director;
|
||||||
|
public NameField[] editor;
|
||||||
|
@JsonProperty("editorial-director")
|
||||||
|
// public NameField[] editorialDirector;
|
||||||
|
// public NameField[] interviewer;
|
||||||
|
// public NameField[] illustrator;
|
||||||
|
// @JsonProperty("original-author")
|
||||||
|
// public NameField[] originalAuthor;
|
||||||
|
// public NameField[] recipient;
|
||||||
|
// @JsonProperty("reviewed-author")
|
||||||
|
// public NameField[] reviewedAuthor;
|
||||||
|
public NameField[] translator;
|
||||||
|
// public DateField accessed;
|
||||||
|
public DateField container;
|
||||||
|
// @JsonProperty("event-date")
|
||||||
|
// public DateField eventDate;
|
||||||
|
public DateField issued;
|
||||||
|
// @JsonProperty("original-date")
|
||||||
|
// public DateField originalDate;
|
||||||
|
public DateField submitted;
|
||||||
|
@JsonProperty("abstract")
|
||||||
|
public String abstractText;
|
||||||
|
// public String annote;
|
||||||
|
// public String archive;
|
||||||
|
// public String archive_location;
|
||||||
|
// public String authority;
|
||||||
|
// @JsonProperty("call-number")
|
||||||
|
// public String callNumber;
|
||||||
|
// @JsonProperty("chapter-number")
|
||||||
|
// public String chapterNumber;
|
||||||
|
// @JsonProperty("citation-number")
|
||||||
|
// public String citationNumber;
|
||||||
|
// @JsonProperty("citation-label")
|
||||||
|
// public String citationLabel;
|
||||||
|
// @JsonProperty("collection-number")
|
||||||
|
// public String collectionNumber;
|
||||||
|
@JsonProperty("container-title")
|
||||||
|
public String containerTitle;
|
||||||
|
// @JsonProperty("container-title-short")
|
||||||
|
// public String containerTitleShort;
|
||||||
|
// public String dimensions;
|
||||||
|
public String DOI;
|
||||||
|
// public String edition; // Integer?
|
||||||
|
public String event;
|
||||||
|
// @JsonProperty("event-place")
|
||||||
|
// public String eventPlace;
|
||||||
|
// @JsonProperty("first-reference-note-number")
|
||||||
|
// public String firstReferenceNoteNumber;
|
||||||
|
// public String genre;
|
||||||
|
public String issue; // Integer?
|
||||||
|
// public String jurisdiction;
|
||||||
|
// public String keyword;
|
||||||
|
// public String locator;
|
||||||
|
// public String medium;
|
||||||
|
public String note;
|
||||||
|
public String number; // Integer?
|
||||||
|
// @JsonProperty("number-of-pages")
|
||||||
|
// public String numberOfPages;
|
||||||
|
// @JsonProperty("number-of-volumes")
|
||||||
|
// public String numberOfVolumes; // Integer?
|
||||||
|
// @JsonProperty("original-publisher")
|
||||||
|
// public String originalPublisher;
|
||||||
|
// @JsonProperty("original-publisher-place")
|
||||||
|
// public String originalPublisherPlace;
|
||||||
|
// @JsonProperty("original-title")
|
||||||
|
// public String originalTitle;
|
||||||
|
public String page;
|
||||||
|
// @JsonProperty("page-first")
|
||||||
|
// public String pageFirst;
|
||||||
|
public String PMCID;
|
||||||
|
public String PMID;
|
||||||
|
public String publisher;
|
||||||
|
// @JsonProperty("publisher-place")
|
||||||
|
// public String publisherPlace;
|
||||||
|
// public String references;
|
||||||
|
// @JsonProperty("reviewed-title")
|
||||||
|
// public String reviewedTitle;
|
||||||
|
public String scale;
|
||||||
|
public String section;
|
||||||
|
public String source;
|
||||||
|
public String status;
|
||||||
|
public String title;
|
||||||
|
// @JsonProperty("title-short")
|
||||||
|
// public String titleShort;
|
||||||
|
public String URL;
|
||||||
|
public String version;
|
||||||
|
public String volume; // Integer?
|
||||||
|
// @JsonProperty("year-suffix")
|
||||||
|
// public String yearSuffix;
|
||||||
|
|
||||||
|
public static class NameField {
|
||||||
|
// Crossref specific fields
|
||||||
|
|
||||||
|
// public String[] affiliation;
|
||||||
|
|
||||||
|
// Standard Citeproc fields
|
||||||
|
|
||||||
|
public String family;
|
||||||
|
public String given;
|
||||||
|
// @JsonProperty("dropping-particle")
|
||||||
|
// public String droppingParticle;
|
||||||
|
// @JsonProperty("non-dropping-particle")
|
||||||
|
// public String nonDroppingParticle;
|
||||||
|
public String suffix;
|
||||||
|
// @JsonProperty("comma-suffix")
|
||||||
|
// public String commaSuffix; // Number? Boolean?
|
||||||
|
// @JsonProperty("staticOrdering")
|
||||||
|
// public String staticOrdering; // Number? Boolean?
|
||||||
|
public String literal;
|
||||||
|
// @JsonProperty("parse-names")
|
||||||
|
// public String parseNames; // Number? Boolean?
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DateField {
|
||||||
|
// Crossref specific fields
|
||||||
|
|
||||||
|
@JsonProperty("date-time")
|
||||||
|
public Date dateTime;
|
||||||
|
// public Long timestamp;
|
||||||
|
|
||||||
|
// Standard Citeproc fields
|
||||||
|
|
||||||
|
@JsonProperty("date-parts")
|
||||||
|
public String[][] dateParts; // Number?
|
||||||
|
// public String season; // Number?
|
||||||
|
// public String circa; // Number? Boolean?
|
||||||
|
public String literal;
|
||||||
|
// public String raw;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package org.vivoweb.webapp.createandlink.crossref;
|
||||||
|
|
||||||
|
import org.vivoweb.webapp.createandlink.Citation;
|
||||||
|
import org.vivoweb.webapp.createandlink.CreateAndLinkResourceProvider;
|
||||||
|
import org.vivoweb.webapp.createandlink.ExternalIdentifiers;
|
||||||
|
import org.vivoweb.webapp.createandlink.ResourceModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider for looking up DOIs in CrossRef
|
||||||
|
*/
|
||||||
|
public class CrossrefCreateAndLinkResourceProvider implements CreateAndLinkResourceProvider {
|
||||||
|
/**
|
||||||
|
* Make a normalized version of the ID
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String normalize(String id) {
|
||||||
|
if (id != null) {
|
||||||
|
// Trim and lower case
|
||||||
|
String doiTrimmed = id.trim().toLowerCase();
|
||||||
|
|
||||||
|
// If we have been passed the resolver URI, strip it down to the bare DOI
|
||||||
|
if (doiTrimmed.startsWith("https://dx.doi.org/")) {
|
||||||
|
return doiTrimmed.substring(19);
|
||||||
|
} else if (doiTrimmed.startsWith("http://dx.doi.org/")) {
|
||||||
|
return doiTrimmed.substring(18);
|
||||||
|
} else if (doiTrimmed.startsWith("https://doi.org/")) {
|
||||||
|
return doiTrimmed.substring(16);
|
||||||
|
} else if (doiTrimmed.startsWith("http://doi.org/")) {
|
||||||
|
return doiTrimmed.substring(15);
|
||||||
|
}
|
||||||
|
|
||||||
|
return doiTrimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for the UI
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getLabel() {
|
||||||
|
return "DOI";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the DOI into other external identifiers
|
||||||
|
*
|
||||||
|
* @param externalId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ExternalIdentifiers allExternalIDsForFind(String externalId) {
|
||||||
|
// For now, just return the DOI
|
||||||
|
ExternalIdentifiers ids = new ExternalIdentifiers();
|
||||||
|
ids.DOI = externalId;
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up the DOI in CrossRef, and populate a citation object
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
* @param citation
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String findInExternal(String id, Citation citation) {
|
||||||
|
// Use content negotiation on the resolver API (wider variety of sources)
|
||||||
|
CrossrefResolverAPI resolverAPI = new CrossrefResolverAPI();
|
||||||
|
String json = resolverAPI.findInExternal(id, citation);
|
||||||
|
|
||||||
|
// If the content negotiation failed, use the CrossRef Native API
|
||||||
|
if (json == null) {
|
||||||
|
CrossrefNativeAPI nativeAPI = new CrossrefNativeAPI();
|
||||||
|
json = nativeAPI.findInExternal(id, citation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the JSON fragment
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an internmediate model of the external resource (JSON string)
|
||||||
|
*
|
||||||
|
* @param externalId
|
||||||
|
* @param externalResource
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResourceModel makeResourceModel(String externalId, String externalResource) {
|
||||||
|
// Note that the external resource may be slightly different, depending on whether it came from
|
||||||
|
// the resolver or native api
|
||||||
|
|
||||||
|
// First, try the resolver API format to create the model
|
||||||
|
CrossrefResolverAPI resolverAPI = new CrossrefResolverAPI();
|
||||||
|
ResourceModel resourceModel = resolverAPI.makeResourceModel(externalResource);
|
||||||
|
|
||||||
|
// Otherwise, try the native API format to create the model
|
||||||
|
if (resourceModel == null) {
|
||||||
|
CrossrefNativeAPI nativeAPI = new CrossrefNativeAPI();
|
||||||
|
resourceModel = nativeAPI.makeResourceModel(externalResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the created resource model
|
||||||
|
return resourceModel;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,368 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package org.vivoweb.webapp.createandlink.crossref;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.utils.http.HttpClientFactory;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.web.URLEncoder;
|
||||||
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.vivoweb.webapp.createandlink.Citation;
|
||||||
|
import org.vivoweb.webapp.createandlink.CreateAndLinkUtils;
|
||||||
|
import org.vivoweb.webapp.createandlink.ResourceModel;
|
||||||
|
import org.vivoweb.webapp.createandlink.utils.HttpReader;
|
||||||
|
import org.vivoweb.webapp.createandlink.utils.StringArrayDeserializer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to CrossRef's native API
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties
|
||||||
|
public class CrossrefNativeAPI {
|
||||||
|
private static final Log log = LogFactory.getLog(CrossrefNativeAPI.class);
|
||||||
|
|
||||||
|
// API endpoint address
|
||||||
|
private static final String CROSSREF_API = "http://api.crossref.org/works/";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the DOI in CrossRef, filling the citation object
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
* @param citation
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String findInExternal(String id, Citation citation) {
|
||||||
|
// Get JSON from the CrossRef API
|
||||||
|
String json = readUrl(CROSSREF_API + URLEncoder.encode(id));
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(json)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
CrossrefResponse response = null;
|
||||||
|
try {
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
response = objectMapper.readValue(json, CrossrefResponse.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Unable to read JSON value", e);
|
||||||
|
}
|
||||||
|
if (response == null || response.message == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The CrossRef API sometimes gives a false record when the DOI deosn't exist
|
||||||
|
// So ensure that the response we got contains the DOI we asked for
|
||||||
|
if (!id.equalsIgnoreCase(response.message.DOI)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map the fields from the CrossRef response to the Citation object
|
||||||
|
|
||||||
|
citation.DOI = id;
|
||||||
|
citation.type = normalizeType(response.message.type);
|
||||||
|
|
||||||
|
if (!ArrayUtils.isEmpty(response.message.title)) {
|
||||||
|
citation.title = response.message.title[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ArrayUtils.isEmpty(response.message.containerTitle)) {
|
||||||
|
for (String journal : response.message.containerTitle) {
|
||||||
|
if (citation.journal == null || citation.journal.length() < journal.length()) {
|
||||||
|
citation.journal = journal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.message.author != null) {
|
||||||
|
List<Citation.Name> authors = new ArrayList<>();
|
||||||
|
for (CrossrefResponse.ResponseModel.Author author : response.message.author) {
|
||||||
|
Citation.Name citationAuthor = new Citation.Name();
|
||||||
|
citationAuthor.name = CreateAndLinkUtils.formatAuthorString(author.family, author.given);
|
||||||
|
authors.add(citationAuthor);
|
||||||
|
}
|
||||||
|
citation.authors = authors.toArray(new Citation.Name[authors.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
citation.volume = response.message.volume;
|
||||||
|
citation.issue = response.message.issue;
|
||||||
|
citation.pagination = response.message.page;
|
||||||
|
if (citation.pagination == null) {
|
||||||
|
citation.pagination = response.message.articleNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
citation.publicationYear = extractYearFromDateField(response.message.publishedPrint);
|
||||||
|
if (citation.publicationYear == null) {
|
||||||
|
citation.publicationYear = extractYearFromDateField(response.message.publishedOnline);
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the year from a compound date field
|
||||||
|
*
|
||||||
|
* @param date
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private Integer extractYearFromDateField(CrossrefResponse.ResponseModel.DateField date) {
|
||||||
|
if (date == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ArrayUtils.isEmpty(date.dateParts)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return date.dateParts[0][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a full resource model from the external resource (JSON)
|
||||||
|
* @param externalResource
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ResourceModel makeResourceModel(String externalResource) {
|
||||||
|
if (StringUtils.isEmpty(externalResource)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
CrossrefResponse response = null;
|
||||||
|
try {
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
response = objectMapper.readValue(externalResource, CrossrefResponse.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Unable to read JSON", e);
|
||||||
|
}
|
||||||
|
if (response == null || response.message == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(response.message.DOI)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map the fields from the CrossRef response to the resource model
|
||||||
|
|
||||||
|
ResourceModel model = new ResourceModel();
|
||||||
|
|
||||||
|
model.DOI = response.message.DOI;
|
||||||
|
model.ISSN = response.message.ISSN;
|
||||||
|
model.URL = response.message.URL;
|
||||||
|
|
||||||
|
if (response.message.author != null && response.message.author.length > 0) {
|
||||||
|
model.author = new ResourceModel.NameField[response.message.author.length];
|
||||||
|
for (int authIdx = 0; authIdx < response.message.author.length; authIdx++) {
|
||||||
|
if (response.message.author[authIdx] != null) {
|
||||||
|
model.author[authIdx] = new ResourceModel.NameField();
|
||||||
|
model.author[authIdx].family = response.message.author[authIdx].family;
|
||||||
|
model.author[authIdx].given = response.message.author[authIdx].given;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (response.message.containerTitle != null && response.message.containerTitle.length > 0) {
|
||||||
|
String journalName = null;
|
||||||
|
for (String container : response.message.containerTitle) {
|
||||||
|
if (journalName == null || container.length() > journalName.length()) {
|
||||||
|
journalName = container;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
model.containerTitle = journalName;
|
||||||
|
}
|
||||||
|
|
||||||
|
model.issue = response.message.issue;
|
||||||
|
|
||||||
|
if (!StringUtils.isEmpty(response.message.page)) {
|
||||||
|
if (response.message.page.contains("-")) {
|
||||||
|
int hyphen = response.message.page.indexOf('-');
|
||||||
|
model.pageStart = response.message.page.substring(0, hyphen);
|
||||||
|
model.pageEnd = response.message.page.substring(hyphen + 1);
|
||||||
|
} else {
|
||||||
|
model.pageStart = response.message.page;
|
||||||
|
}
|
||||||
|
} else if (!StringUtils.isEmpty(response.message.articleNumber)) {
|
||||||
|
model.pageStart = response.message.articleNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
model.publicationDate = convertDateField(response.message.publishedPrint);
|
||||||
|
if (model.publicationDate == null) {
|
||||||
|
model.publicationDate = convertDateField(response.message.publishedOnline);
|
||||||
|
}
|
||||||
|
|
||||||
|
model.publisher = response.message.publisher;
|
||||||
|
model.subject = response.message.subject;
|
||||||
|
if (response.message.title != null && response.message.title.length > 0) {
|
||||||
|
model.title = response.message.title[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
model.type = normalizeType(response.message.type);
|
||||||
|
model.volume = response.message.volume;
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map non-standard publication types into the CiteProc types
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String normalizeType(String type) {
|
||||||
|
if (type != null) {
|
||||||
|
switch (type.toLowerCase()) {
|
||||||
|
case "journal-article":
|
||||||
|
return "article-journal";
|
||||||
|
|
||||||
|
case "book-chapter":
|
||||||
|
return "chapter";
|
||||||
|
|
||||||
|
case "proceedings-article":
|
||||||
|
return "paper-conference";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a date field from the CrossRef response to the internal resource model format
|
||||||
|
*
|
||||||
|
* @param dateField
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private ResourceModel.DateField convertDateField(CrossrefResponse.ResponseModel.DateField dateField) {
|
||||||
|
if (dateField != null) {
|
||||||
|
ResourceModel.DateField resourceDate = new ResourceModel.DateField();
|
||||||
|
if (dateField.dateParts != null && dateField.dateParts.length > 0 && dateField.dateParts[0].length > 0) {
|
||||||
|
if (dateField.dateParts.length == 1) {
|
||||||
|
resourceDate.year = dateField.dateParts[0][0];
|
||||||
|
} else if (dateField.dateParts.length == 2) {
|
||||||
|
resourceDate.year = dateField.dateParts[0][0];
|
||||||
|
resourceDate.month = dateField.dateParts[0][1];
|
||||||
|
} else {
|
||||||
|
resourceDate.year = dateField.dateParts[0][0];
|
||||||
|
resourceDate.month = dateField.dateParts[0][1];
|
||||||
|
resourceDate.day = dateField.dateParts[0][2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resourceDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read JSON from the given URL
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String readUrl(String url) {
|
||||||
|
try {
|
||||||
|
HttpClient client = HttpClientFactory.getHttpClient();
|
||||||
|
HttpGet request = new HttpGet(url);
|
||||||
|
HttpResponse response = client.execute(request);
|
||||||
|
return HttpReader.fromResponse(response);
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java object representation of the JSON returned by CrossRef
|
||||||
|
*/
|
||||||
|
private static class CrossrefResponse {
|
||||||
|
public ResponseModel message;
|
||||||
|
|
||||||
|
@JsonProperty("message-type")
|
||||||
|
public String messageType;
|
||||||
|
|
||||||
|
@JsonProperty("message-version")
|
||||||
|
public String messageVersion;
|
||||||
|
|
||||||
|
public String status;
|
||||||
|
|
||||||
|
public static class ResponseModel {
|
||||||
|
public String DOI;
|
||||||
|
@JsonDeserialize(using = StringArrayDeserializer.class)
|
||||||
|
public String[] ISSN;
|
||||||
|
public String URL;
|
||||||
|
|
||||||
|
@JsonProperty("alternative-id")
|
||||||
|
@JsonDeserialize(using = StringArrayDeserializer.class)
|
||||||
|
public String[] alternativeId;
|
||||||
|
|
||||||
|
public Author[] author;
|
||||||
|
|
||||||
|
@JsonProperty("container-title")
|
||||||
|
@JsonDeserialize(using = StringArrayDeserializer.class)
|
||||||
|
public String[] containerTitle;
|
||||||
|
public DateField created;
|
||||||
|
public DateField deposited;
|
||||||
|
public DateField indexed;
|
||||||
|
public String issue;
|
||||||
|
public DateField issued;
|
||||||
|
public String member;
|
||||||
|
public String page;
|
||||||
|
public String prefix;
|
||||||
|
|
||||||
|
@JsonProperty("article-number")
|
||||||
|
public String articleNumber;
|
||||||
|
|
||||||
|
@JsonProperty("published-online")
|
||||||
|
public DateField publishedOnline;
|
||||||
|
|
||||||
|
@JsonProperty("published-print")
|
||||||
|
public DateField publishedPrint;
|
||||||
|
|
||||||
|
public String publisher;
|
||||||
|
|
||||||
|
@JsonProperty("reference-count")
|
||||||
|
public Integer referenceCount;
|
||||||
|
public Double score;
|
||||||
|
@JsonDeserialize(using = StringArrayDeserializer.class)
|
||||||
|
public String[] subject;
|
||||||
|
@JsonDeserialize(using = StringArrayDeserializer.class)
|
||||||
|
public String[] subtitle;
|
||||||
|
@JsonDeserialize(using = StringArrayDeserializer.class)
|
||||||
|
public String[] title;
|
||||||
|
public String type;
|
||||||
|
public String volume;
|
||||||
|
|
||||||
|
|
||||||
|
public static class Author {
|
||||||
|
@JsonDeserialize(using = StringArrayDeserializer.class)
|
||||||
|
public String[] affiliation;
|
||||||
|
public String family;
|
||||||
|
public String given;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DateField {
|
||||||
|
@JsonProperty("date-parts")
|
||||||
|
public Integer[][] dateParts;
|
||||||
|
|
||||||
|
@JsonProperty("date-time")
|
||||||
|
public Date dateTime;
|
||||||
|
|
||||||
|
public Long timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,392 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package org.vivoweb.webapp.createandlink.crossref;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.utils.http.HttpClientFactory;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.web.URLEncoder;
|
||||||
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.vivoweb.webapp.createandlink.Citation;
|
||||||
|
import org.vivoweb.webapp.createandlink.CreateAndLinkUtils;
|
||||||
|
import org.vivoweb.webapp.createandlink.ResourceModel;
|
||||||
|
import org.vivoweb.webapp.createandlink.utils.HttpReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to the CrossRef resolver
|
||||||
|
*/
|
||||||
|
public class CrossrefResolverAPI {
|
||||||
|
protected final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
|
// Base URL for the resolver
|
||||||
|
private static final String CROSSREF_RESOLVER = "https://doi.org/";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the DOI in CrossRef, filling the citation object
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
* @param citation
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String findInExternal(String id, Citation citation) {
|
||||||
|
try {
|
||||||
|
// Read JSON from the resolver
|
||||||
|
String json = readJSON(CROSSREF_RESOLVER + URLEncoder.encode(id));
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(json)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
CrossrefCiteprocJSONModel jsonModel = objectMapper.readValue(json, CrossrefCiteprocJSONModel.class);
|
||||||
|
if (jsonModel == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that we have the correct resource
|
||||||
|
if (!id.equalsIgnoreCase(jsonModel.DOI)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map the fields of the resolver response to the citation object
|
||||||
|
|
||||||
|
citation.DOI = id;
|
||||||
|
citation.type = normalizeType(jsonModel.type);
|
||||||
|
citation.title = jsonModel.title;
|
||||||
|
citation.journal = jsonModel.containerTitle;
|
||||||
|
|
||||||
|
if (jsonModel.author != null) {
|
||||||
|
List<Citation.Name> authors = new ArrayList<>();
|
||||||
|
for (CrossrefCiteprocJSONModel.NameField author : jsonModel.author) {
|
||||||
|
splitNameLiteral(author);
|
||||||
|
Citation.Name citationAuthor = new Citation.Name();
|
||||||
|
citationAuthor.name = CreateAndLinkUtils.formatAuthorString(author.family, author.given);
|
||||||
|
authors.add(citationAuthor);
|
||||||
|
}
|
||||||
|
citation.authors = authors.toArray(new Citation.Name[authors.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
citation.volume = jsonModel.volume;
|
||||||
|
citation.issue = jsonModel.issue;
|
||||||
|
citation.pagination = jsonModel.page;
|
||||||
|
if (citation.pagination == null) {
|
||||||
|
citation.pagination = jsonModel.articleNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
citation.publicationYear = extractYearFromDateField(jsonModel.publishedPrint);
|
||||||
|
if (citation.publicationYear == null) {
|
||||||
|
citation.publicationYear = extractYearFromDateField(jsonModel.publishedOnline);
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("[CREF] Error resolving DOI " + id + ", cause "+ e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the year from the crossref JSON model
|
||||||
|
*
|
||||||
|
* @param date
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private Integer extractYearFromDateField(CrossrefCiteprocJSONModel.DateField date) {
|
||||||
|
if (date == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ArrayUtils.isEmpty(date.dateParts)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Integer.parseInt(date.dateParts[0][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param externalResource
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ResourceModel makeResourceModel(String externalResource) {
|
||||||
|
if (StringUtils.isEmpty(externalResource)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
CrossrefCiteprocJSONModel jsonModel = null;
|
||||||
|
try {
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
jsonModel = objectMapper.readValue(externalResource, CrossrefCiteprocJSONModel.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Unable to read JSON", e);
|
||||||
|
}
|
||||||
|
if (jsonModel == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(jsonModel.DOI)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map the fields of the Java object to the resource model
|
||||||
|
|
||||||
|
ResourceModel model = new ResourceModel();
|
||||||
|
|
||||||
|
model.DOI = jsonModel.DOI;
|
||||||
|
model.PubMedID = jsonModel.PMID;
|
||||||
|
model.PubMedCentralID = jsonModel.PMCID;
|
||||||
|
model.ISSN = jsonModel.ISSN;
|
||||||
|
model.ISBN = jsonModel.ISBN;
|
||||||
|
model.URL = jsonModel.URL;
|
||||||
|
|
||||||
|
if (jsonModel.ISBN != null) {
|
||||||
|
int isbnIdx = 0;
|
||||||
|
model.ISBN = new String[jsonModel.ISBN.length];
|
||||||
|
for (String isbn : jsonModel.ISBN) {
|
||||||
|
if (isbn.lastIndexOf('/') > -1) {
|
||||||
|
isbn = isbn.substring(isbn.lastIndexOf('/') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
model.ISBN[isbnIdx] = isbn;
|
||||||
|
isbnIdx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
model.author = convertNameFields(jsonModel.author);
|
||||||
|
model.editor = convertNameFields(jsonModel.editor);
|
||||||
|
model.translator = convertNameFields(jsonModel.translator);
|
||||||
|
|
||||||
|
model.containerTitle = jsonModel.containerTitle;
|
||||||
|
|
||||||
|
model.issue = jsonModel.issue;
|
||||||
|
|
||||||
|
if (!StringUtils.isEmpty(jsonModel.page)) {
|
||||||
|
if (jsonModel.page.contains("-")) {
|
||||||
|
int hyphen = jsonModel.page.indexOf('-');
|
||||||
|
model.pageStart = jsonModel.page.substring(0, hyphen);
|
||||||
|
model.pageEnd = jsonModel.page.substring(hyphen + 1);
|
||||||
|
} else {
|
||||||
|
model.pageStart = jsonModel.page;
|
||||||
|
}
|
||||||
|
} else if (!StringUtils.isEmpty(jsonModel.articleNumber)) {
|
||||||
|
model.pageStart = jsonModel.articleNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
model.publicationDate = convertDateField(jsonModel.publishedPrint);
|
||||||
|
if (model.publicationDate == null) {
|
||||||
|
model.publicationDate = convertDateField(jsonModel.publishedOnline);
|
||||||
|
}
|
||||||
|
|
||||||
|
model.publisher = jsonModel.publisher;
|
||||||
|
model.subject = jsonModel.subject;
|
||||||
|
model.title = jsonModel.title;
|
||||||
|
model.type = normalizeType(jsonModel.type);
|
||||||
|
model.volume = jsonModel.volume;
|
||||||
|
|
||||||
|
model.status = jsonModel.status;
|
||||||
|
model.presentedAt = jsonModel.event;
|
||||||
|
model.abstractText = jsonModel.abstractText;
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert CiteProc name fields into resource model name fields
|
||||||
|
*
|
||||||
|
* @param nameFields
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private ResourceModel.NameField[] convertNameFields(CrossrefCiteprocJSONModel.NameField[] nameFields) {
|
||||||
|
if (nameFields == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceModel.NameField[] destNameFields = new ResourceModel.NameField[nameFields.length];
|
||||||
|
|
||||||
|
for (int nameIdx = 0; nameIdx < nameFields.length; nameIdx++) {
|
||||||
|
if (nameFields[nameIdx] != null) {
|
||||||
|
splitNameLiteral(nameFields[nameIdx]);
|
||||||
|
destNameFields[nameIdx] = new ResourceModel.NameField();
|
||||||
|
destNameFields[nameIdx].family = nameFields[nameIdx].family;
|
||||||
|
destNameFields[nameIdx].given = nameFields[nameIdx].given;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return destNameFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map non-standard publication types into the CiteProc types
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String normalizeType(String type) {
|
||||||
|
if (type != null) {
|
||||||
|
switch (type.toLowerCase()) {
|
||||||
|
case "journal-article":
|
||||||
|
return "article-journal";
|
||||||
|
|
||||||
|
case "book-chapter":
|
||||||
|
return "chapter";
|
||||||
|
|
||||||
|
case "proceedings-article":
|
||||||
|
return "paper-conference";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split a name literal into first and last names
|
||||||
|
*
|
||||||
|
* @param author
|
||||||
|
*/
|
||||||
|
private void splitNameLiteral(CrossrefCiteprocJSONModel.NameField author) {
|
||||||
|
if (StringUtils.isEmpty(author.family)) {
|
||||||
|
String given = null;
|
||||||
|
if (!StringUtils.isEmpty(author.literal)) {
|
||||||
|
if (author.literal.contains(",")) {
|
||||||
|
author.family = author.literal.substring(0, author.literal.indexOf(','));
|
||||||
|
given = author.literal.substring(author.literal.indexOf(',') + 1);
|
||||||
|
} else if (author.literal.lastIndexOf(' ') > -1) {
|
||||||
|
author.family = author.literal.substring(author.literal.lastIndexOf(' ') + 1);
|
||||||
|
given = author.literal.substring(0, author.literal.lastIndexOf(' '));
|
||||||
|
} else {
|
||||||
|
author.family = author.literal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(author.given)) {
|
||||||
|
author.given = given;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a CiteProc date field to resource model date field
|
||||||
|
*
|
||||||
|
* @param dateField
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private ResourceModel.DateField convertDateField(CrossrefCiteprocJSONModel.DateField dateField) {
|
||||||
|
if (dateField != null) {
|
||||||
|
ResourceModel.DateField resourceDate = new ResourceModel.DateField();
|
||||||
|
if (dateField.dateParts != null && dateField.dateParts.length > 0 && dateField.dateParts[0].length > 0) {
|
||||||
|
try {
|
||||||
|
resourceDate.year = Integer.parseInt(dateField.dateParts[0][0], 10);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
}
|
||||||
|
if (dateField.dateParts.length > 1) {
|
||||||
|
try {
|
||||||
|
resourceDate.month = Integer.parseInt(dateField.dateParts[0][1], 10);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
switch (dateField.dateParts[0][1].toLowerCase()) {
|
||||||
|
case "jan":
|
||||||
|
case "january":
|
||||||
|
resourceDate.month = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "feb":
|
||||||
|
case "february":
|
||||||
|
resourceDate.month = 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "mar":
|
||||||
|
case "march":
|
||||||
|
resourceDate.month = 3;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "apr":
|
||||||
|
case "april":
|
||||||
|
resourceDate.month = 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "may":
|
||||||
|
resourceDate.month = 5;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "jun":
|
||||||
|
case "june":
|
||||||
|
resourceDate.month = 6;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "jul":
|
||||||
|
case "july":
|
||||||
|
resourceDate.month = 7;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "aug":
|
||||||
|
case "august":
|
||||||
|
resourceDate.month = 8;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "sep":
|
||||||
|
case "september":
|
||||||
|
resourceDate.month = 9;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "oct":
|
||||||
|
case "october":
|
||||||
|
resourceDate.month = 10;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "nov":
|
||||||
|
case "november":
|
||||||
|
resourceDate.month = 11;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "dec":
|
||||||
|
case "december":
|
||||||
|
resourceDate.month = 12;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dateField.dateParts.length > 2) {
|
||||||
|
try {
|
||||||
|
resourceDate.day = Integer.parseInt(dateField.dateParts[0][2], 10);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resourceDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read JSON from the URL
|
||||||
|
* @param url
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String readJSON(String url) {
|
||||||
|
try {
|
||||||
|
HttpClient client = HttpClientFactory.getHttpClient();
|
||||||
|
HttpGet request = new HttpGet(url);
|
||||||
|
|
||||||
|
// Content negotiate for csl / citeproc JSON
|
||||||
|
request.setHeader("Accept", "application/vnd.citationstyles.csl+json;q=1.0");
|
||||||
|
|
||||||
|
HttpResponse response = client.execute(request);
|
||||||
|
return HttpReader.fromResponse(response);
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,377 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package org.vivoweb.webapp.createandlink.pubmed;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonFactory;
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.core.JsonToken;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.utils.http.HttpClientFactory;
|
||||||
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.vivoweb.webapp.createandlink.Citation;
|
||||||
|
import org.vivoweb.webapp.createandlink.CreateAndLinkResourceProvider;
|
||||||
|
import org.vivoweb.webapp.createandlink.ExternalIdentifiers;
|
||||||
|
import org.vivoweb.webapp.createandlink.ResourceModel;
|
||||||
|
import org.vivoweb.webapp.createandlink.utils.HttpReader;
|
||||||
|
import org.vivoweb.webapp.createandlink.utils.StringArrayDeserializer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class PubMedCreateAndLinkResourceProvider implements CreateAndLinkResourceProvider {
|
||||||
|
protected final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
|
public final static String PUBMED_ID_API = "http://www.ncbi.nlm.nih.gov/pmc/utils/idconv/v1.0/?format=json&ids=";
|
||||||
|
public final static String PUBMED_SUMMARY_API = "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&tool=my_tool&email=my_email@example.com&id=";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String normalize(String id) {
|
||||||
|
return id.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLabel() {
|
||||||
|
return "PubMed ID";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExternalIdentifiers allExternalIDsForFind(String externalId) {
|
||||||
|
ExternalIdentifiers ids = new ExternalIdentifiers();
|
||||||
|
ids.PubMedID = externalId;
|
||||||
|
|
||||||
|
String json = readUrl(PUBMED_ID_API + externalId);
|
||||||
|
if (!StringUtils.isEmpty(json)) {
|
||||||
|
PubMedIDResponse response = null;
|
||||||
|
try {
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
response = objectMapper.readValue(json, PubMedIDResponse.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Unable to read JSON", e);
|
||||||
|
}
|
||||||
|
if (response != null && !ArrayUtils.isEmpty(response.records)) {
|
||||||
|
ids.DOI = response.records[0].doi;
|
||||||
|
ids.PubMedCentralID = response.records[0].pmcid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String findInExternal(String id, Citation citation) {
|
||||||
|
try {
|
||||||
|
String json = readUrl(PUBMED_SUMMARY_API + id);
|
||||||
|
if (StringUtils.isEmpty(json)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonFactory factory = new JsonFactory();
|
||||||
|
JsonParser parser = factory.createParser(json);
|
||||||
|
if (parser != null) {
|
||||||
|
while (!parser.isClosed() && !id.equals(parser.getCurrentName())) {
|
||||||
|
JsonToken token = parser.nextToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parser.isClosed()) {
|
||||||
|
// We have reached the field for our ID, but we need to be on the next token for the mapper to work
|
||||||
|
JsonToken token = parser.nextToken();
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
PubMedSummaryResponse response = objectMapper.readValue(parser, PubMedSummaryResponse.class);
|
||||||
|
if (response != null) {
|
||||||
|
citation.title = response.title;
|
||||||
|
citation.authors = new Citation.Name[response.authors.length];
|
||||||
|
for (int idx = 0; idx < response.authors.length; idx++) {
|
||||||
|
citation.authors[idx] = new Citation.Name();
|
||||||
|
citation.authors[idx].name = normalizeAuthorName(response.authors[idx].name);
|
||||||
|
}
|
||||||
|
citation.journal = response.fulljournalname;
|
||||||
|
citation.volume = response.volume;
|
||||||
|
citation.issue = response.issue;
|
||||||
|
citation.pagination = response.pages;
|
||||||
|
if (!StringUtils.isEmpty(response.pubdate) && response.pubdate.length() >= 4) {
|
||||||
|
citation.publicationYear = Integer.parseInt(response.pubdate.substring(0, 4), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
citation.type = getCiteprocTypeForPubType(response.pubtype);
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("[PMID] Error resolving PMID " + id + ", cause "+ e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResourceModel makeResourceModel(String externalId, String externalResource) {
|
||||||
|
try {
|
||||||
|
JsonFactory factory = new JsonFactory();
|
||||||
|
JsonParser parser = factory.createParser(externalResource);
|
||||||
|
if (parser != null) {
|
||||||
|
while (!parser.isClosed() && !externalId.equals(parser.getCurrentName())) {
|
||||||
|
JsonToken token = parser.nextToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parser.isClosed()) {
|
||||||
|
// We have reached the field for our ID, but we need to be on the next token for the mapper to work
|
||||||
|
JsonToken token = parser.nextToken();
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
PubMedSummaryResponse response = objectMapper.readValue(parser, PubMedSummaryResponse.class);
|
||||||
|
if (response != null) {
|
||||||
|
ResourceModel resourceModel = new ResourceModel();
|
||||||
|
resourceModel.PubMedID = externalId;
|
||||||
|
resourceModel.title = response.title;
|
||||||
|
resourceModel.author = new ResourceModel.NameField[response.authors.length];
|
||||||
|
for (int idx = 0; idx < response.authors.length; idx++) {
|
||||||
|
resourceModel.author[idx] = new ResourceModel.NameField();
|
||||||
|
if (response.authors[idx].name.lastIndexOf(' ') > 0) {
|
||||||
|
resourceModel.author[idx].family = response.authors[idx].name.substring(0, response.authors[idx].name.lastIndexOf(' '));
|
||||||
|
resourceModel.author[idx].given = response.authors[idx].name.substring(response.authors[idx].name.lastIndexOf(' ') + 1);
|
||||||
|
} else {
|
||||||
|
resourceModel.author[idx].family = response.authors[idx].name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceModel.containerTitle = response.fulljournalname;
|
||||||
|
if (!StringUtils.isEmpty(response.issn)) {
|
||||||
|
resourceModel.ISSN = new String[1];
|
||||||
|
resourceModel.ISSN[0] = response.issn;
|
||||||
|
} else if (!StringUtils.isEmpty(response.eissn)) {
|
||||||
|
resourceModel.ISSN = new String[1];
|
||||||
|
resourceModel.ISSN[0] = response.eissn;
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceModel.volume = response.volume;
|
||||||
|
resourceModel.issue = response.issue;
|
||||||
|
if (response.pages.contains("-")) {
|
||||||
|
int hyphen = response.pages.indexOf('-');
|
||||||
|
resourceModel.pageStart = response.pages.substring(0, hyphen);
|
||||||
|
resourceModel.pageEnd = response.pages.substring(hyphen + 1);
|
||||||
|
} else {
|
||||||
|
resourceModel.pageStart = response.pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StringUtils.isEmpty(response.pubdate) && response.pubdate.length() >= 4) {
|
||||||
|
resourceModel.publicationDate = new ResourceModel.DateField();
|
||||||
|
resourceModel.publicationDate.year = Integer.parseInt(response.pubdate.substring(0, 4), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.articleids != null) {
|
||||||
|
for (PubMedSummaryResponse.ArticleID articleID : response.articleids) {
|
||||||
|
if (!StringUtils.isEmpty(articleID.value)) {
|
||||||
|
if ("doi".equalsIgnoreCase(articleID.idtype)) {
|
||||||
|
resourceModel.DOI = articleID.value.trim();
|
||||||
|
} else if ("pmc".equalsIgnoreCase(articleID.idtype)) {
|
||||||
|
resourceModel.PubMedCentralID = articleID.value.trim();
|
||||||
|
} else if ("pmcid".equalsIgnoreCase(articleID.idtype)) {
|
||||||
|
if (StringUtils.isEmpty(resourceModel.PubMedCentralID)) {
|
||||||
|
String id = articleID.value.replaceAll(".*(PMC[0-9]+).*", "$1");
|
||||||
|
if (!StringUtils.isEmpty(id)) {
|
||||||
|
resourceModel.PubMedCentralID = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceModel.type = getCiteprocTypeForPubType(response.pubtype);
|
||||||
|
resourceModel.publisher = response.publishername;
|
||||||
|
resourceModel.status = response.pubstatus;
|
||||||
|
|
||||||
|
/*
|
||||||
|
public DateField created;
|
||||||
|
public String[] subject;
|
||||||
|
public String presentedAt;
|
||||||
|
public String[] keyword;
|
||||||
|
public String abstractText;
|
||||||
|
*/
|
||||||
|
return resourceModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Unable to read JSON", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeAuthorName(String name) {
|
||||||
|
if (name.indexOf(',') < 0 && name.indexOf(' ') > -1) {
|
||||||
|
int lastSpace = name.lastIndexOf(' ');
|
||||||
|
int insertPoint = lastSpace;
|
||||||
|
while (insertPoint > 0) {
|
||||||
|
if (name.charAt(insertPoint - 1) == ' ') {
|
||||||
|
insertPoint--;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return name.substring(0, insertPoint) + "," + name.substring(lastSpace);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCiteprocTypeForPubType(String[] pubTypes) {
|
||||||
|
if (pubTypes != null && pubTypes.length > 0) {
|
||||||
|
for (String pubType : pubTypes) {
|
||||||
|
switch (pubType) {
|
||||||
|
case "Journal Article":
|
||||||
|
return "article-journal";
|
||||||
|
|
||||||
|
case "Incunabula":
|
||||||
|
case "Monograph":
|
||||||
|
case "Textbooks":
|
||||||
|
return "book";
|
||||||
|
|
||||||
|
case "Dataset":
|
||||||
|
return "dataset";
|
||||||
|
|
||||||
|
case "Legal Cases":
|
||||||
|
return "legal_case";
|
||||||
|
|
||||||
|
case "Legislation":
|
||||||
|
return "legislation";
|
||||||
|
|
||||||
|
case "Manuscripts":
|
||||||
|
return "manuscript";
|
||||||
|
|
||||||
|
case "Maps":
|
||||||
|
return "map";
|
||||||
|
|
||||||
|
case "Meeting Abstracts":
|
||||||
|
return "paper-conference";
|
||||||
|
|
||||||
|
case "Patents":
|
||||||
|
return "patent";
|
||||||
|
|
||||||
|
case "Letter":
|
||||||
|
return "personal_communication";
|
||||||
|
|
||||||
|
case "Blogs":
|
||||||
|
return "post-weblog";
|
||||||
|
|
||||||
|
case "Review":
|
||||||
|
return "review";
|
||||||
|
|
||||||
|
case "Academic Dissertations":
|
||||||
|
return "thesis";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "article-journal";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readUrl(String url) {
|
||||||
|
try {
|
||||||
|
HttpClient client = HttpClientFactory.getHttpClient();
|
||||||
|
HttpGet request = new HttpGet(url);
|
||||||
|
HttpResponse response = client.execute(request);
|
||||||
|
return HttpReader.fromResponse(response);
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PubMedIDResponse {
|
||||||
|
public String status;
|
||||||
|
public String responseDate;
|
||||||
|
public String request;
|
||||||
|
public String warning;
|
||||||
|
|
||||||
|
public PubMedIDRecord[] records;
|
||||||
|
|
||||||
|
public static class PubMedIDRecord {
|
||||||
|
String pmcid;
|
||||||
|
String pmid;
|
||||||
|
String doi;
|
||||||
|
|
||||||
|
// Don't need versions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PubMedSummaryResponse {
|
||||||
|
public String uid;
|
||||||
|
public String pubdate;
|
||||||
|
//public String epubdate;
|
||||||
|
public String source;
|
||||||
|
public NameField[] authors;
|
||||||
|
//public String lastauthor;
|
||||||
|
public String title;
|
||||||
|
//public String sorttitle;
|
||||||
|
public String volume;
|
||||||
|
public String issue;
|
||||||
|
public String pages;
|
||||||
|
@JsonDeserialize(using = StringArrayDeserializer.class)
|
||||||
|
public String[] lang;
|
||||||
|
//public String nlmuniqueid;
|
||||||
|
public String issn;
|
||||||
|
public String eissn;
|
||||||
|
@JsonDeserialize(using = StringArrayDeserializer.class)
|
||||||
|
public String[] pubtype;
|
||||||
|
//public String recordstatus;
|
||||||
|
public String pubstatus;
|
||||||
|
public ArticleID[] articleids;
|
||||||
|
public History[] history;
|
||||||
|
//public String[] references;
|
||||||
|
@JsonDeserialize(using = StringArrayDeserializer.class)
|
||||||
|
public String[] attributes;
|
||||||
|
//public Integer pmcrefcount;
|
||||||
|
public String fulljournalname;
|
||||||
|
//public String elocationid;
|
||||||
|
//public Integer viewcount;
|
||||||
|
//public String doctype;
|
||||||
|
//public String[] srccontriblist;
|
||||||
|
//public String booktitle;
|
||||||
|
//public String medium;
|
||||||
|
//public String edition;
|
||||||
|
//public String publisherlocation;
|
||||||
|
public String publishername;
|
||||||
|
//public String srcdate;
|
||||||
|
//public String reportnumber;
|
||||||
|
//public String availablefromurl;
|
||||||
|
//public String locationlabel;
|
||||||
|
//public String[] doccontriblist;
|
||||||
|
//public String docdate;
|
||||||
|
//public String bookname;
|
||||||
|
public String chapter;
|
||||||
|
//public String sortpubdate;
|
||||||
|
//public String sortfirstauthor;
|
||||||
|
//public String vernaculartitle;
|
||||||
|
|
||||||
|
public static class NameField {
|
||||||
|
public String name;
|
||||||
|
//public String authtype;
|
||||||
|
//public String clusterid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ArticleID {
|
||||||
|
public String idtype;
|
||||||
|
//public Integer idtypen;
|
||||||
|
public String value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class History {
|
||||||
|
public String pubstatus;
|
||||||
|
public String date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package org.vivoweb.webapp.createandlink.utils;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
|
||||||
|
public class HttpReader {
|
||||||
|
public static String fromResponse(HttpResponse response) throws IOException {
|
||||||
|
HttpEntity entity = response != null ? response.getEntity() : null;
|
||||||
|
try {
|
||||||
|
if (entity != null) {
|
||||||
|
if (response.getStatusLine().getStatusCode() == 200) {
|
||||||
|
try (InputStream in = entity.getContent()) {
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
IOUtils.copy(in, writer, "UTF-8");
|
||||||
|
return writer.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (entity != null) {
|
||||||
|
EntityUtils.consume(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package org.vivoweb.webapp.createandlink.utils;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.core.JsonToken;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class StringArrayDeserializer extends JsonDeserializer<String[]> {
|
||||||
|
@Override
|
||||||
|
public String[] deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
|
||||||
|
if (JsonToken.VALUE_NULL.equals(jsonParser.getCurrentToken())) {
|
||||||
|
jsonParser.nextToken();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JsonToken.START_ARRAY.equals(jsonParser.getCurrentToken())) {
|
||||||
|
List<String> list = new ArrayList<>();
|
||||||
|
while (!JsonToken.END_ARRAY.equals(jsonParser.nextToken())) {
|
||||||
|
list.add(jsonParser.getValueAsString());
|
||||||
|
}
|
||||||
|
return list.toArray(new String[list.size()]);
|
||||||
|
} else if (JsonToken.VALUE_STRING.equals(jsonParser.getCurrentToken())) {
|
||||||
|
return new String[] { jsonParser.getText() };
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -375,8 +375,14 @@ Vitro.reconcile.defaultTypeList = http://vivoweb.org/ontology/core#Role, core:Ro
|
||||||
http://xmlns.com/foaf/0.1/Person, foaf:Person; \
|
http://xmlns.com/foaf/0.1/Person, foaf:Person; \
|
||||||
http://purl.obolibrary.org/obo/IAO_0000030, obo:IAO_0000030
|
http://purl.obolibrary.org/obo/IAO_0000030, obo:IAO_0000030
|
||||||
|
|
||||||
|
# Configure the support for claiming by DOI or PMID
|
||||||
|
# This is a list of all the providers that are active for claiming articles from
|
||||||
|
# Options: doi, pmid
|
||||||
|
# which search Crossref and PubMed, respectively
|
||||||
|
# If you do not wish to use the claiming interface, set this property to nothing (empty)
|
||||||
|
createAndLink.providers = doi, pmid
|
||||||
|
|
||||||
# Triple pattern fragments is a very fast, very simple means for querying a triple store.
|
# Triple pattern fragments is a very fast, very simple means for querying a triple store.
|
||||||
# The triple pattern fragments API in VIVO puts little load on the server, providing a simple means for getting data from the triple store. The API has a web interface for manual use, can be used from the command line via curl, and can be used by programs.
|
# The triple pattern fragments API in VIVO puts little load on the server, providing a simple means for getting data from the triple store. The API has a web interface for manual use, can be used from the command line via curl, and can be used by programs.
|
||||||
|
#
|
||||||
# tpf.activeFlag = true
|
# tpf.activeFlag = true
|
|
@ -810,3 +810,55 @@ role_in_presentation_capitalized=Role in Presentation
|
||||||
advisee_capitalized_first_name=First Name
|
advisee_capitalized_first_name=First Name
|
||||||
advisee_capitalized_lastname=Last Name
|
advisee_capitalized_lastname=Last Name
|
||||||
|
|
||||||
|
# Messages for creating and linking resources (publications)
|
||||||
|
create_and_link_enter=Enter {0}:
|
||||||
|
create_and_link_claim_for=Claiming works for<br />{0}
|
||||||
|
create_and_link_confirm_works=Confirm your work(s)
|
||||||
|
create_and_link_confirm_works_intro=Please check that these are the work(s) that you wish to claim, and indicate your relationship with them.
|
||||||
|
create_and_link_authors=Authors
|
||||||
|
create_and_link_authors_desc=If you are an author of a work, please select your name in the author list.<br />Retrieved metadata may be incomplete. If you can not see your name listed, select "Unlisted Author".
|
||||||
|
create_and_link_editors=Editors
|
||||||
|
create_and_link_editors_desc=If you edited the work, please select "Editor".
|
||||||
|
create_and_link_not_mine_desc=If you do not wish to claim a work, select "This is not my work".
|
||||||
|
create_and_link_already_claimed=You have already claimed this work.
|
||||||
|
create_and_link_unlisted_author=Unlisted Author
|
||||||
|
create_and_link_editor=Editor
|
||||||
|
create_and_link_not_mine=This is not my work
|
||||||
|
create_and_link_remaining=There are {0} ids remaining
|
||||||
|
create_and_link_thank_you=Thank you
|
||||||
|
create_and_link_finished=There are no more works left to claim.<br />You may enter more IDs below, or view your profile.
|
||||||
|
create_and_link_go_profile=Go to profile
|
||||||
|
create_and_link_enter_dois_intro=You may enter one or more DOIs to match, and can be entered either as an ID or URL:<br /><br />e.g.
|
||||||
|
create_and_link_enter_dois_supported=Currently, DOIs issued by Crossref, DataCite and mEDRA are supported.<br />Each DOI should be separated by a comma or new line.
|
||||||
|
create_and_link_enter_pmid_intro=You may enter one or more PubMed IDs to match. Each ID should be separated by a comma or new line.
|
||||||
|
create_and_link_enter_pmid_supported=Note that metadata will be retrieved from Crossref, if the PubMed ID can be resolved to a DOI.
|
||||||
|
create_and_link_unknown_profile=Unknown Profile
|
||||||
|
create_and_link_unknown_resource=Unknown Resource Type
|
||||||
|
create_and_link_unauthorized_for_profile=You do not have permissions to claim for this user
|
||||||
|
create_and_link_submit_ids=Submit IDs
|
||||||
|
create_and_link_submit_confirm=Confirm
|
||||||
|
create_and_link_error=Unable to retrieve citation details
|
||||||
|
create_and_link_type_article=Article
|
||||||
|
create_and_link_type_article_journal=Journal Article
|
||||||
|
create_and_link_type_book=Book
|
||||||
|
create_and_link_type_chapter=Chapter
|
||||||
|
create_and_link_type_dataset=Dataset
|
||||||
|
create_and_link_type_figure=Image
|
||||||
|
create_and_link_type_graphic=Image
|
||||||
|
create_and_link_type_legal_case=Legal Case
|
||||||
|
create_and_link_type_legislation=Legislation
|
||||||
|
create_and_link_type_manuscript=Manuscript
|
||||||
|
create_and_link_type_map=Map
|
||||||
|
create_and_link_type_musical_score=Musical Score
|
||||||
|
create_and_link_type_paper_conference=Conference Paper
|
||||||
|
create_and_link_type_patent=Patent
|
||||||
|
create_and_link_type_personal_communication=Letter
|
||||||
|
create_and_link_type_post_weblog=Blog
|
||||||
|
create_and_link_type_report=Report
|
||||||
|
create_and_link_type_review=Review
|
||||||
|
create_and_link_type_speech=Speech
|
||||||
|
create_and_link_type_thesis=Thesis
|
||||||
|
create_and_link_type_webpage=Webpage
|
||||||
|
claim_publications_by=Claim publications by
|
||||||
|
claim_publications_by_doi=DOI
|
||||||
|
claim_publications_by_pmid=PubMed ID
|
BIN
webapp/src/main/webapp/images/createAndLink/error.png
Normal file
BIN
webapp/src/main/webapp/images/createAndLink/error.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
BIN
webapp/src/main/webapp/images/createAndLink/tick.png
Normal file
BIN
webapp/src/main/webapp/images/createAndLink/tick.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.3 KiB |
|
@ -0,0 +1,111 @@
|
||||||
|
<#setting number_format="computer">
|
||||||
|
<form id="createAndLink" method="post">
|
||||||
|
<#if personLabel??>
|
||||||
|
<div class="claim-for">
|
||||||
|
<h3>${i18n().create_and_link_claim_for(personLabel)}</h3>
|
||||||
|
<#if personThumbUrl??>
|
||||||
|
<img src="${urls.base}${personThumbUrl}" />
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
<h2>${i18n().create_and_link_confirm_works}</h2>
|
||||||
|
${i18n().create_and_link_confirm_works_intro}<br /><br />
|
||||||
|
<h4>${i18n().create_and_link_authors}</h4>
|
||||||
|
<div class="description">${i18n().create_and_link_authors_desc}</div><br />
|
||||||
|
<h4>${i18n().create_and_link_editors}</h4>
|
||||||
|
<div class="description">${i18n().create_and_link_editors_desc}</div><br /><br />
|
||||||
|
${i18n().create_and_link_not_mine_desc}<br /><br />
|
||||||
|
<#list citations as citation>
|
||||||
|
<div class="entryId">
|
||||||
|
<#if citation.externalProvider??>
|
||||||
|
${citation.externalProvider?upper_case}: ${citation.externalId?html}
|
||||||
|
<#else>
|
||||||
|
ID: ${citation.externalId?html}
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
<#if citation.type?has_content>
|
||||||
|
<select name="type${citation.externalId}">
|
||||||
|
<#list publicationTypes as publicationType>
|
||||||
|
<option value="${publicationType.uri}" <#if publicationType.uri == citation.typeUri>selected</#if>>${publicationType.label}</option>
|
||||||
|
</#list>
|
||||||
|
</select><br/>
|
||||||
|
</#if>
|
||||||
|
<div class="entry">
|
||||||
|
<#if citation.showError>
|
||||||
|
<div class="citation_error">
|
||||||
|
${i18n().create_and_link_error}
|
||||||
|
</div>
|
||||||
|
<#else>
|
||||||
|
<!-- Output Citation -->
|
||||||
|
<#if citation.alreadyClaimed>
|
||||||
|
<div class="citation_claimed">
|
||||||
|
</#if>
|
||||||
|
<input type="hidden" name="externalId" value="${citation.externalId!}" />
|
||||||
|
<div class="citation">
|
||||||
|
<#assign proposedAuthor=false />
|
||||||
|
<#if citation.title??><span class="citation_title">${citation.title?html}</span><br /></#if>
|
||||||
|
<#assign formatted_citation>
|
||||||
|
<#if citation.journal??><span class="citation_journal">${citation.journal?html}</span></#if>
|
||||||
|
<#if citation.publicationYear??><span class="citation_year">${citation.publicationYear!?html};</span></#if>
|
||||||
|
<#if citation.volume??><span class="citation_volume">${citation.volume!?html}</#if>
|
||||||
|
<#if citation.issue??><span class="citation_issue">(${citation.issue!?html})</#if>
|
||||||
|
<#if citation.pagination??><span class="citation_pages">:${citation.pagination!?html}</#if>
|
||||||
|
</#assign>
|
||||||
|
<#if formatted_citation??>
|
||||||
|
${formatted_citation}<br />
|
||||||
|
</#if>
|
||||||
|
<#if citation.authors??>
|
||||||
|
<#list citation.authors as author>
|
||||||
|
<#if author??>
|
||||||
|
<span class="citation_author">
|
||||||
|
<#if citation.alreadyClaimed>
|
||||||
|
<span>${author.name!?html}</span>
|
||||||
|
<#else>
|
||||||
|
<#if author.name??>
|
||||||
|
<#if !author.linked>
|
||||||
|
<input type="radio" id="author${citation.externalId}-${author?counter}" name="contributor${citation.externalId}" value="author${author?counter}" <#if author.proposed>checked</#if> class="radioWithLabel" />
|
||||||
|
<label for="author${citation.externalId}-${author?counter}" class="labelForRadio">${author.name!?html}</label>
|
||||||
|
<#if author.proposed><#assign proposedAuthor=true /></#if>
|
||||||
|
<#else>
|
||||||
|
<span class="linked">${author.name!?html}</span>
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
</span>
|
||||||
|
</#if>
|
||||||
|
<#sep>; </#sep>
|
||||||
|
</#list><br />
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<#if citation.alreadyClaimed>
|
||||||
|
<span class="claimed">${i18n().create_and_link_already_claimed}</span>
|
||||||
|
<#else>
|
||||||
|
<input type="radio" id="author${citation.externalId}" name="contributor${citation.externalId}" value="author" <#if !proposedAuthor>checked</#if> class="radioWithLabel" /><label for="author${citation.externalId}" class="labelForRadio"> ${i18n().create_and_link_unlisted_author}</label><br />
|
||||||
|
<input type="radio" id="editor${citation.externalId}" name="contributor${citation.externalId}" value="editor" class="radioWithLabel" /><label for="editor${citation.externalId}" class="labelForRadio"> ${i18n().create_and_link_editor}</label><br />
|
||||||
|
<input type="radio" id="notmine${citation.externalId}" name="contributor${citation.externalId}" value="notmine" class="radioWithLabel" /><label for="notmine${citation.externalId}" class="labelForRadio"> ${i18n().create_and_link_not_mine}</label><br />
|
||||||
|
</#if>
|
||||||
|
<input type="hidden" name="externalResource${citation.externalId}" value="${citation.externalResource!?html}" />
|
||||||
|
<input type="hidden" name="externalProvider${citation.externalId}" value="${citation.externalProvider!?html}" />
|
||||||
|
<input type="hidden" name="vivoUri${citation.externalId}" value="${citation.vivoUri!?html}" />
|
||||||
|
<input type="hidden" name="profileUri" value="${profileUri!}" />
|
||||||
|
<#if citation.alreadyClaimed>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
<div style="clear: both;"></div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<!-- End Citation -->
|
||||||
|
</#list>
|
||||||
|
<#if remainderIds??>
|
||||||
|
<input type="hidden" name="remainderIds" value="${remainderIds}" />
|
||||||
|
</#if>
|
||||||
|
<div class="buttons">
|
||||||
|
<input type="hidden" name="action" value="confirmID" />
|
||||||
|
<input type="submit" value="${i18n().create_and_link_submit_confirm}" class="submit" />
|
||||||
|
<#if remainderCount??>
|
||||||
|
<span class="remainder">${i18n().create_and_link_remaining(remainderCount)}</span>
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
</form>
|
|
@ -0,0 +1,35 @@
|
||||||
|
<form id="createAndLink" method="post">
|
||||||
|
<#if personLabel??>
|
||||||
|
<div class="claim-for">
|
||||||
|
<h3>${i18n().create_and_link_claim_for(personLabel)}</h3>
|
||||||
|
<#if personThumbUrl??>
|
||||||
|
<img src="${urls.base}${personThumbUrl}" />
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
<#if showConfirmation??>
|
||||||
|
<h2>${i18n().create_and_link_thank_you}</h2>
|
||||||
|
${i18n().create_and_link_finished}<br /><br />
|
||||||
|
<#if profileUri??>
|
||||||
|
<a href="${profileUrl(profileUri)}">${i18n().create_and_link_go_profile}</a><br /><br />
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
<h2>${i18n().create_and_link_enter(label)}</h2>
|
||||||
|
<#switch provider>
|
||||||
|
<#case "doi">
|
||||||
|
${i18n().create_and_link_enter_dois_intro}<br />
|
||||||
|
<i>ID</i>: 10.1038/nature01234<br />
|
||||||
|
<i>URL</i>: https://doi.org/10.1038/nature01234<br />
|
||||||
|
<br />
|
||||||
|
${i18n().create_and_link_enter_dois_supported}<br /><br />
|
||||||
|
<#break>
|
||||||
|
<#case "pmid">
|
||||||
|
${i18n().create_and_link_enter_pmid_intro}<br /><br />
|
||||||
|
${i18n().create_and_link_enter_pmid_supported}<br /><br />
|
||||||
|
<#break>
|
||||||
|
</#switch>
|
||||||
|
<textarea name="externalIds" rows="15" cols="50"></textarea><br />
|
||||||
|
<input type="submit" value="${i18n().create_and_link_submit_ids}" class="submit" /><br />
|
||||||
|
<input type="hidden" name="action" value="findID" />
|
||||||
|
<input type="hidden" name="profileUri" value="${profileUri!}" />
|
||||||
|
</form>
|
|
@ -0,0 +1 @@
|
||||||
|
<h2>${i18n().create_and_link_unauthorized_for_profile}</h2>
|
|
@ -0,0 +1,2 @@
|
||||||
|
<h2>${i18n().create_and_link_unknown_profile}</h2>
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
<h2>${i18n().create_and_link_unknown_resource}</h2>
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
#createAndLink select {
|
||||||
|
height: 2.5em;
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
padding-bottom: 0px;
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#createAndLink .citation_error:before {
|
||||||
|
content: url('../../../images/createAndLink/error.png');
|
||||||
|
transform: scale(0.5);
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
#createAndLink .citation_error {
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
#createAndLink .citation_claimed:before {
|
||||||
|
content: url('../../../images/createAndLink/tick.png');
|
||||||
|
transform: scale(0.75);
|
||||||
|
margin-top: -17px;
|
||||||
|
margin-left: 535px;
|
||||||
|
float: left;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
#createAndLink .citation_claimed:hover:before {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
#createAndLink .citation_claimed .citation {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
#createAndLink .citation_claimed:hover .citation {
|
||||||
|
opacity: 1.0;
|
||||||
|
}
|
||||||
|
#createAndLink .citation_type {
|
||||||
|
font-style: italic;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
#createAndLink .citation_title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#createAndLink .citation_journal {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
#createAndLink .claimed {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#createAndLink .linked {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
#createAndLink .entryId {
|
||||||
|
background-color: #3e8baa; /* #E0E0E0; */
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
#createAndLink .entry {
|
||||||
|
border: 2px solid #3e8baa; /* #E0E0E0; */
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
#createAndLink label {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
#createAndLink .radioWithLabel:checked + .labelForRadio {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#createAndLink .description {
|
||||||
|
padding-left: 22px;
|
||||||
|
}
|
||||||
|
#createAndLink .remainder {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
#createAndLink .claim-for {
|
||||||
|
float: right;
|
||||||
|
border: 2px solid #3e8baa; /* #E0E0E0; */
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
#createAndLink .claim-for h3 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
|
@ -30,5 +30,6 @@ VIVO tenderfoot theme: screen styles
|
||||||
@import url("page-individual.css");
|
@import url("page-individual.css");
|
||||||
@import url("page-login.css");
|
@import url("page-login.css");
|
||||||
@import url("page-menu.css");
|
@import url("page-menu.css");
|
||||||
|
@import url("page-createAndLink.css");
|
||||||
@import url("https://fonts.googleapis.com/css?family=Noto+Sans");
|
@import url("https://fonts.googleapis.com/css?family=Noto+Sans");
|
||||||
@import url("../../../local/css/local.css");
|
@import url("../../../local/css/local.css");
|
||||||
|
|
|
@ -104,11 +104,23 @@
|
||||||
|
|
||||||
<section itemscope itemtype="http://schema.org/Person" id="individual-intro" class="vcard person" role="region">
|
<section itemscope itemtype="http://schema.org/Person" id="individual-intro" class="vcard person" role="region">
|
||||||
<section id="individual-info" ${infoClass!} role="region">
|
<section id="individual-info" ${infoClass!} role="region">
|
||||||
<!-- Overview -->
|
<#if editable>
|
||||||
<!-- #include "individual-overview.ftl" -->
|
<#if claimSources?size > 0>
|
||||||
|
${i18n().claim_publications_by}
|
||||||
<!-- Geographic Focus -->
|
<#if claimSources?seq_contains("doi")>
|
||||||
<!-- #include "individual-geographicFocus.ftl" -->
|
<form action="${urls.base}/createAndLink/doi" method="get" style="display: inline-block;">
|
||||||
|
<input type="hidden" name="profileUri" value="${individual.uri}" />
|
||||||
|
<input type="submit" class="submit" value="${i18n().claim_publications_by_doi}" />
|
||||||
|
</form>
|
||||||
|
</#if>
|
||||||
|
<#if claimSources?seq_contains("pmid")>
|
||||||
|
<form action="${urls.base}/createAndLink/pmid" method="get" style="display: inline-block;">
|
||||||
|
<input type="hidden" name="profileUri" value="${individual.uri}" />
|
||||||
|
<input type="submit" class="submit" value="${i18n().claim_publications_by_pmid}" />
|
||||||
|
</form>
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
#createAndLink select {
|
||||||
|
height: 2.5em;
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
padding-bottom: 0px;
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#createAndLink .citation_error:before {
|
||||||
|
content: url('../../../images/createAndLink/error.png');
|
||||||
|
transform: scale(0.5);
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
#createAndLink .citation_error {
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
#createAndLink .citation_claimed:before {
|
||||||
|
content: url('../../../images/createAndLink/tick.png');
|
||||||
|
transform: scale(0.75);
|
||||||
|
margin-top: -17px;
|
||||||
|
margin-left: 535px;
|
||||||
|
float: left;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
#createAndLink .citation_claimed:hover:before {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
#createAndLink .citation_claimed .citation {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
#createAndLink .citation_claimed:hover .citation {
|
||||||
|
opacity: 1.0;
|
||||||
|
}
|
||||||
|
#createAndLink .citation_type {
|
||||||
|
font-style: italic;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
#createAndLink .citation_title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#createAndLink .citation_journal {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
#createAndLink .claimed {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#createAndLink .linked {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
#createAndLink .entryId {
|
||||||
|
background-color: #3e8baa; /* #E0E0E0; */
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
#createAndLink .entry {
|
||||||
|
border: 2px solid #3e8baa; /* #E0E0E0; */
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
#createAndLink label {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
#createAndLink .radioWithLabel:checked + .labelForRadio {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#createAndLink .description {
|
||||||
|
padding-left: 22px;
|
||||||
|
}
|
||||||
|
#createAndLink .remainder {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
#createAndLink .claim-for {
|
||||||
|
float: right;
|
||||||
|
border: 2px solid #3e8baa; /* #E0E0E0; */
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
#createAndLink .claim-for h3 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
|
@ -24,4 +24,5 @@ VIVO wilma theme: screen styles
|
||||||
|
|
||||||
@import url("reset.css");
|
@import url("reset.css");
|
||||||
@import url("wilma.css");
|
@import url("wilma.css");
|
||||||
|
@import url("page-createAndLink.css");
|
||||||
@import url("../../../local/css/local.css");
|
@import url("../../../local/css/local.css");
|
||||||
|
|
|
@ -62,6 +62,23 @@
|
||||||
<section id="individual-info" ${infoClass!} role="region">
|
<section id="individual-info" ${infoClass!} role="region">
|
||||||
<section id="right-hand-column" role="region">
|
<section id="right-hand-column" role="region">
|
||||||
<#include "individual-visualizationFoafPerson.ftl">
|
<#include "individual-visualizationFoafPerson.ftl">
|
||||||
|
<#if editable>
|
||||||
|
<#if claimSources?size > 0>
|
||||||
|
<br />${i18n().claim_publications_by}<br />
|
||||||
|
<#if claimSources?seq_contains("doi")>
|
||||||
|
<form action="${urls.base}/createAndLink/doi" method="get" style="float: left;">
|
||||||
|
<input type="hidden" name="profileUri" value="${individual.uri}" />
|
||||||
|
<input type="submit" class="submit" value="${i18n().claim_publications_by_doi}" />
|
||||||
|
</form>
|
||||||
|
</#if>
|
||||||
|
<#if claimSources?seq_contains("pmid")>
|
||||||
|
<form action="${urls.base}/createAndLink/pmid" method="get" style="float: right;">
|
||||||
|
<input type="hidden" name="profileUri" value="${individual.uri}" />
|
||||||
|
<input type="submit" class="submit" value="${i18n().claim_publications_by_pmid}" />
|
||||||
|
</form>
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
</section>
|
</section>
|
||||||
<#include "individual-adminPanel.ftl">
|
<#include "individual-adminPanel.ftl">
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue