/** * ============================================================================= * * ORCID (R) Open Source * http://orcid.org * * Copyright (c) 2012-2014 ORCID, Inc. * Licensed under an MIT-Style License (MIT) * http://orcid.org/open-source-license * * This copyright and license information (including a link to the full license) * shall be included in its entirety in all copies or substantial portion of * the software. * * ============================================================================= */ package org.orcid.core.manager.impl; import java.util.ArrayList; import java.util.List; import javax.annotation.Resource; import org.apache.commons.lang3.StringUtils; import org.orcid.core.manager.ActivitiesSummaryManager; import org.orcid.core.manager.BibtexManager; import org.orcid.core.manager.DOIManager; import org.orcid.core.manager.ProfileEntityManager; import org.orcid.core.manager.WorkManager; import org.orcid.jaxb.model.common_v2.Contributor; import org.orcid.jaxb.model.record.summary_v2.ActivitiesSummary; import org.orcid.jaxb.model.record.summary_v2.WorkGroup; import org.orcid.jaxb.model.record.summary_v2.WorkSummary; import org.orcid.jaxb.model.record_rc1.WorkExternalIdentifierType; import org.orcid.jaxb.model.record_v2.CitationType; import org.orcid.jaxb.model.record_v2.ExternalID; import org.orcid.jaxb.model.record_v2.Work; import org.orcid.persistence.jpa.entities.ProfileEntity; import org.orcid.persistence.jpa.entities.RecordNameEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.MappingIterator; import com.fasterxml.jackson.dataformat.csv.CsvMapper; import com.fasterxml.jackson.dataformat.csv.CsvParser; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableMap; public class BibtexManagerImpl implements BibtexManager{ private static final Logger LOGGER = LoggerFactory.getLogger(BibtexManagerImpl.class); @Resource private ActivitiesSummaryManager activitiesManager; @Resource private WorkManager workManager; @Resource private ProfileEntityManager profileEntityManager; @Resource private DOIManager doiManager; private static volatile ImmutableMap<Character,String> escapeW3C = null; private static Object initLock = new Object(); public BibtexManagerImpl(){ if (escapeW3C == null){ synchronized(initLock){ if (escapeW3C == null){ CsvMapper mapper = new CsvMapper(); mapper.enable(CsvParser.Feature.WRAP_AS_ARRAY); try { MappingIterator<String[]> it = mapper.reader(String[].class).readValues(getClass().getResourceAsStream("escape_bibtex.txt")); ImmutableMap.Builder<Character,String> builder = new ImmutableMap.Builder<Character,String>(); while (it.hasNext()){ String[] row = it.next(); if (row.length == 2) builder.put((char)row[1].trim().charAt(0), row[0]); } escapeW3C = builder.build(); } catch (Exception e) { throw new RuntimeException(e); } } } } } @Override public String generateBibtexReferenceList(String orcid) { long last = workManager.getLastModified(orcid); ActivitiesSummary summary = activitiesManager.getActivitiesSummary(orcid); List<String> citations = new ArrayList<String>(); if (summary.getWorks()!=null){ for (WorkGroup group : summary.getWorks().getWorkGroup()){ WorkSummary workSummary = group.getWorkSummary().get(0); Work work = workManager.getWork(orcid, workSummary.getPutCode(), last); String bibtex = generateBibtex(orcid,work); if (bibtex != null) citations.add(bibtex); } } return Joiner.on(",\n").join(citations); } @Override public String generateBibtex(String orcid, Work work){ //if we have a citation use that if (work.getWorkCitation() != null && work.getWorkCitation().getWorkCitationType() != null && work.getWorkCitation().getWorkCitationType().equals(CitationType.BIBTEX)) { return work.getWorkCitation().getCitation(); } //if we have a DOI, use that if (work.getWorkExternalIdentifiers() != null && work.getWorkExternalIdentifiers().getExternalIdentifier() != null){ String doi = extractID(work, WorkExternalIdentifierType.DOI); if (doi != null){ try{ String bibtex = doiManager.fetchDOIBibtex(doi); if (bibtex != null) return bibtex; }catch (Exception e){ //something went wrong at crossref/datacite e.g. 10.1890/1540-9295(2006)004[0244:elsdvs]2.0.co;2 //ignore and use our metadata LOGGER.warn("cannot resolve DOI to metadata:"+doi); } } } //otherwise, use whatever we can String creditName = getCreditName(orcid); return workToBibtex(work,creditName); } public String workToBibtex(Work work, String creditName){ StringBuffer out = new StringBuffer(); switch (work.getWorkType()) { case JOURNAL_ARTICLE: out.append("@article{"); break; case BOOK: case BOOK_CHAPTER: out.append("@book{"); break; case CONFERENCE_PAPER: case CONFERENCE_ABSTRACT: case CONFERENCE_POSTER: out.append("@conference{"); break; default: out.append("@misc{"); break; } //id out.append(escapeStringForBibtex(creditName).replace(' ', '_')+work.getPutCode()); //title out.append(",\ntitle={"+escapeStringForBibtex((work.getWorkTitle() != null) ? work.getWorkTitle().getTitle().getContent() : "No Title")+"}"); //journal title if (work.getJournalTitle() != null) { out.append(",\njournal={"+escapeStringForBibtex(work.getJournalTitle().getContent())+"}"); } //name List<String> names = new ArrayList<String>(); names.add(creditName); if (work.getWorkContributors() != null && work.getWorkContributors().getContributor() != null) { for (Contributor c : work.getWorkContributors().getContributor()) { if (c.getCreditName() != null && c.getCreditName().getContent() != null) { names.add(c.getCreditName().getContent()); } } } out.append(",\nauthor={"+escapeStringForBibtex(Joiner.on(" and ").skipNulls().join(names))+"}"); //ids String doi = extractID(work, WorkExternalIdentifierType.DOI); String url = extractID(work, WorkExternalIdentifierType.URI); if (doi != null) { out.append(",\ndoi={"+escapeStringForBibtex(doi)+"}"); } if (url != null) { out.append(",\nurl={"+escapeStringForBibtex(url)+"}"); } else if (doi != null) { out.append(",\nurl={"+escapeStringForBibtex("http://doi.org/" + doi)+"}"); } else { url = extractID(work, WorkExternalIdentifierType.HANDLE); if (url != null) { out.append(",\nurl={"+escapeStringForBibtex(url)+"}"); } } String isbn = extractID(work, WorkExternalIdentifierType.ISBN); if (isbn != null) out.append(",\nisbn={"+escapeStringForBibtex(isbn)+"}"); String issn = extractID(work, WorkExternalIdentifierType.ISSN); if (issn !=null) out.append(",\nissn={"+escapeStringForBibtex(issn)+"}"); //year if (work.getPublicationDate() != null) { int year = 0; try { year = Integer.parseInt(work.getPublicationDate().getYear().getValue()); } catch (Exception e) { } if (year > 0) { out.append(",\nyear={"+year+"}"); } } out.append("\n}"); return out.toString(); } /** * Extract a credit name from the profile * @param orcid * @return */ private String getCreditName(String orcid){ ProfileEntity entity = profileEntityManager.findByOrcid(orcid); String creditName = null; RecordNameEntity recordNameEntity = entity.getRecordNameEntity(); if(recordNameEntity != null) { creditName = recordNameEntity.getCreditName(); if (StringUtils.isBlank(creditName)) { creditName = recordNameEntity.getGivenNames(); String familyName = recordNameEntity.getFamilyName(); if (StringUtils.isNotBlank(familyName)) { creditName += " " + familyName; } } } return creditName; } /** * Merges in the DOI from a work into a CSLItemdata (if found and not * already present) * * @param work * @param item */ private String extractID(Work work, WorkExternalIdentifierType type) { if (work.getExternalIdentifiers() != null && work.getExternalIdentifiers().getExternalIdentifier() != null && work.getExternalIdentifiers().getExternalIdentifier().size() > 0) { for (ExternalID id : work.getExternalIdentifiers().getExternalIdentifier()) { if (id.getType().equalsIgnoreCase(type.value())) { return id.getValue(); } } } return null; } //from https://github.com/datacite/content-resolver/issues/2 //this is the same as datacite and pangaea public final String escapeStringForBibtex(String text) { StringBuilder sb=new StringBuilder(text.length()); boolean nl=false; for (int codepoint : text.codePoints().toArray()){ char ch=(char)codepoint;//text.charAt(i); //for (int i=0 ; i < text.length();i++){ //char ch = text.charAt(i); if (ch!=13 && ch!=10 && nl) { sb.append("\\\\\n"); nl=false; } switch (ch) { case '\u00E4': sb.append("{\\\"a}"); break; case '\u00F6': sb.append("{\\\"o}"); break; case '\u00FC': sb.append("{\\\"u}"); break; case '\u00EB': sb.append("{\\\"e}"); break; case '\u00EF': sb.append("{\\\"i}"); break; case 196: sb.append("{\\\"A}"); break; case 214: sb.append("{\\\"O}"); break; case 220: sb.append("{\\\"U}"); break; case 203: sb.append("{\\\"E}"); break; case 207: sb.append("{\\\"I}"); break; case 225: sb.append("{\\'a}"); break; case 243: sb.append("{\\'o}"); break; case 250: sb.append("{\\'u}"); break; case 233: sb.append("{\\'e}"); break; case 237: sb.append("{\\'i}"); break; case 224: sb.append("{\\`a}"); break; case 242: sb.append("{\\`o}"); break; case 249: sb.append("{\\`u}"); break; case 232: sb.append("{\\`e}"); break; case 236: sb.append("{\\`i}"); break; case 226: sb.append("{\\^a}"); break; case 244: sb.append("{\\^o}"); break; case 251: sb.append("{\\^u}"); break; case 234: sb.append("{\\^e}"); break; case 238: sb.append("{\\^i}"); break; case 194: sb.append("{\\^A}"); break; case 212: sb.append("{\\^O}"); break; case 219: sb.append("{\\^U}"); break; case 202: sb.append("{\\^E}"); break; case 206: sb.append("{\\^I}"); break; case 227: sb.append("{\\~a}"); break; case 241: sb.append("{\\~n}"); break; case 245: sb.append("{\\~o}"); break; case 195: sb.append("{\\~A}"); break; case 209: sb.append("{\\~N}"); break; case 213: sb.append("{\\~O}"); break; case '\u00DF': sb.append("{\\ss}"); break; case '\u00A0': sb.append('~'); break; //   case '\u00BA': sb.append("{\\textdegree}"); break; case '"': sb.append("{\"}"); break; case 13: case 10: nl=true; break; case '\'': case '\u00B4': case '`': sb.append("{\'}"); break; // simple escapes: case '\\': case '~': case '$': case '%': case '^': case '&': case '{': case '}': case '_': sb.append('\\'); sb.append(ch); break; default: if (ch<0x80) sb.append(ch); else { String rep = escapeW3C.get(ch); if (rep != null) sb.append(rep); else sb.append(ch); //here we just spit out the utf-8. This is what GS does... } } } return sb.toString(); } }