package org.jabref.logic.exporter;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.namespace.QName;
import org.jabref.logic.importer.fileformat.mods.AbstractDefinition;
import org.jabref.logic.importer.fileformat.mods.CodeOrText;
import org.jabref.logic.importer.fileformat.mods.DateDefinition;
import org.jabref.logic.importer.fileformat.mods.DetailDefinition;
import org.jabref.logic.importer.fileformat.mods.ExtentDefinition;
import org.jabref.logic.importer.fileformat.mods.GenreDefinition;
import org.jabref.logic.importer.fileformat.mods.IdentifierDefinition;
import org.jabref.logic.importer.fileformat.mods.IssuanceDefinition;
import org.jabref.logic.importer.fileformat.mods.LanguageDefinition;
import org.jabref.logic.importer.fileformat.mods.LanguageTermDefinition;
import org.jabref.logic.importer.fileformat.mods.LocationDefinition;
import org.jabref.logic.importer.fileformat.mods.ModsCollectionDefinition;
import org.jabref.logic.importer.fileformat.mods.ModsDefinition;
import org.jabref.logic.importer.fileformat.mods.NameDefinition;
import org.jabref.logic.importer.fileformat.mods.NamePartDefinition;
import org.jabref.logic.importer.fileformat.mods.NoteDefinition;
import org.jabref.logic.importer.fileformat.mods.OriginInfoDefinition;
import org.jabref.logic.importer.fileformat.mods.PartDefinition;
import org.jabref.logic.importer.fileformat.mods.PhysicalLocationDefinition;
import org.jabref.logic.importer.fileformat.mods.PlaceDefinition;
import org.jabref.logic.importer.fileformat.mods.PlaceTermDefinition;
import org.jabref.logic.importer.fileformat.mods.RelatedItemDefinition;
import org.jabref.logic.importer.fileformat.mods.StringPlusLanguage;
import org.jabref.logic.importer.fileformat.mods.StringPlusLanguagePlusAuthority;
import org.jabref.logic.importer.fileformat.mods.StringPlusLanguagePlusSupplied;
import org.jabref.logic.importer.fileformat.mods.SubjectDefinition;
import org.jabref.logic.importer.fileformat.mods.TitleInfoDefinition;
import org.jabref.logic.importer.fileformat.mods.TypeOfResourceDefinition;
import org.jabref.logic.importer.fileformat.mods.UrlDefinition;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.FieldName;
/**
* ExportFormat for exporting in MODS XML format.
*/
class ModsExportFormat extends ExportFormat {
protected static final String MODS_NAMESPACE_URI = "http://www.loc.gov/mods/v3";
private static final String MINUS = "-";
private static final String DOUBLE_MINUS = "--";
private static final String MODS_SCHEMA_LOCATION = "http://www.loc.gov/standards/mods/v3/mods-3-6.xsd";
private JAXBContext context;
public ModsExportFormat() {
super("MODS", "mods", null, null, ".xml");
}
@Override
public void performExport(final BibDatabaseContext databaseContext, final String file, final Charset encoding,
List<BibEntry> entries) throws SaveException, IOException {
Objects.requireNonNull(databaseContext);
Objects.requireNonNull(entries);
if (entries.isEmpty()) { // Only export if entries exist
return;
}
try {
ModsCollectionDefinition modsCollection = new ModsCollectionDefinition();
for (BibEntry bibEntry : entries) {
ModsDefinition mods = new ModsDefinition();
bibEntry.getCiteKeyOptional().ifPresent(citeKey -> addIdentifier("citekey", citeKey, mods));
Map<String, String> fieldMap = bibEntry.getFieldMap();
addGenre(bibEntry, mods);
OriginInfoDefinition originInfo = new OriginInfoDefinition();
PartDefinition partDefinition = new PartDefinition();
RelatedItemDefinition relatedItem = new RelatedItemDefinition();
for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
switch (key) {
case FieldName.AUTHOR:
handleAuthors(mods, value);
break;
case "affiliation":
addAffiliation(mods, value);
break;
case FieldName.ABSTRACT:
addAbstract(mods, value);
break;
case FieldName.TITLE:
addTitle(mods, value);
break;
case FieldName.LANGUAGE:
addLanguage(mods, value);
break;
case FieldName.LOCATION:
addLocation(mods, value);
break;
case FieldName.URL:
addUrl(mods, value);
break;
case FieldName.NOTE:
addNote(mods, value);
break;
case FieldName.KEYWORDS:
addKeyWords(mods, value);
break;
case FieldName.VOLUME:
addDetail(FieldName.VOLUME, value, partDefinition);
break;
case FieldName.ISSUE:
addDetail(FieldName.ISSUE, value, partDefinition);
break;
case FieldName.PAGES:
addPages(partDefinition, value);
break;
case FieldName.URI:
addIdentifier(FieldName.URI, value, mods);
break;
case FieldName.ISBN:
addIdentifier(FieldName.ISBN, value, mods);
break;
case FieldName.ISSN:
addIdentifier(FieldName.ISSN, value, mods);
break;
case FieldName.DOI:
addIdentifier(FieldName.DOI, value, mods);
break;
case FieldName.PMID:
addIdentifier(FieldName.PMID, value, mods);
break;
case FieldName.JOURNAL:
addJournal(value, relatedItem);
break;
default:
break;
}
addOriginInformation(key, value, originInfo);
}
mods.getModsGroup().add(originInfo);
addRelatedAndOriginInfoToModsGroup(relatedItem, partDefinition, mods);
modsCollection.getMods().add(mods);
}
JAXBElement<ModsCollectionDefinition> jaxbElement = new JAXBElement<>(
new QName(MODS_NAMESPACE_URI, "modsCollection"), ModsCollectionDefinition.class, modsCollection);
createMarshallerAndWriteToFile(file, jaxbElement);
} catch (JAXBException ex) {
throw new SaveException(ex);
}
}
private void createMarshallerAndWriteToFile(String file, JAXBElement<ModsCollectionDefinition> jaxbElement)
throws JAXBException {
if (context == null) {
context = JAXBContext.newInstance(ModsCollectionDefinition.class);
}
Marshaller marshaller = context.createMarshaller();
//format the output
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, MODS_SCHEMA_LOCATION);
// Write to File
marshaller.marshal(jaxbElement, new File(file));
}
private void addRelatedAndOriginInfoToModsGroup(RelatedItemDefinition relatedItem, PartDefinition partDefinition,
ModsDefinition mods) {
relatedItem.getModsGroup().add(partDefinition);
relatedItem.setAtType("host");
mods.getModsGroup().add(relatedItem);
TypeOfResourceDefinition typeOfResource = new TypeOfResourceDefinition();
typeOfResource.setValue("text");
mods.getModsGroup().add(typeOfResource);
}
private void addGenre(BibEntry bibEntry, ModsDefinition mods) {
GenreDefinition genre = new GenreDefinition();
genre.setValue(bibEntry.getType());
mods.getModsGroup().add(genre);
}
private void addAbstract(ModsDefinition mods, String value) {
AbstractDefinition abstractDefinition = new AbstractDefinition();
abstractDefinition.setValue(value);
mods.getModsGroup().add(abstractDefinition);
}
private void addTitle(ModsDefinition mods, String value) {
TitleInfoDefinition titleInfo = new TitleInfoDefinition();
StringPlusLanguage title = new StringPlusLanguage();
title.setValue(value);
JAXBElement<StringPlusLanguage> element = new JAXBElement<>(new QName(MODS_NAMESPACE_URI, "title"),
StringPlusLanguage.class, title);
titleInfo.getTitleOrSubTitleOrPartNumber().add(element);
mods.getModsGroup().add(titleInfo);
}
private void addAffiliation(ModsDefinition mods, String value) {
NameDefinition nameDefinition = new NameDefinition();
StringPlusLanguage affiliation = new StringPlusLanguage();
affiliation.setValue(value);
JAXBElement<StringPlusLanguage> element = new JAXBElement<>(new QName(MODS_NAMESPACE_URI, "affiliation"),
StringPlusLanguage.class, affiliation);
nameDefinition.getAffiliationOrRoleOrDescription().add(element);
mods.getModsGroup().add(nameDefinition);
}
private void addLocation(ModsDefinition mods, String value) {
LocationDefinition locationDefinition = new LocationDefinition();
//There can be more than one location
String[] locations = value.split(", ");
for (String location : locations) {
PhysicalLocationDefinition physicalLocation = new PhysicalLocationDefinition();
physicalLocation.setValue(location);
locationDefinition.getPhysicalLocation().add(physicalLocation);
}
mods.getModsGroup().add(locationDefinition);
}
private void addNote(ModsDefinition mods, String value) {
String[] notes = value.split(", ");
for (String note : notes) {
NoteDefinition noteDefinition = new NoteDefinition();
noteDefinition.setValue(note);
mods.getModsGroup().add(noteDefinition);
}
}
private void addUrl(ModsDefinition mods, String value) {
String[] urls = value.split(", ");
LocationDefinition location = new LocationDefinition();
for (String url : urls) {
UrlDefinition urlDefinition = new UrlDefinition();
urlDefinition.setValue(url);
location.getUrl().add(urlDefinition);
mods.getModsGroup().add(location);
}
}
private void addJournal(String value, RelatedItemDefinition relatedItem) {
TitleInfoDefinition titleInfo = new TitleInfoDefinition();
StringPlusLanguage title = new StringPlusLanguage();
title.setValue(value);
JAXBElement<StringPlusLanguage> element = new JAXBElement<>(new QName(MODS_NAMESPACE_URI, "title"),
StringPlusLanguage.class, title);
titleInfo.getTitleOrSubTitleOrPartNumber().add(element);
relatedItem.getModsGroup().add(titleInfo);
}
private void addLanguage(ModsDefinition mods, String value) {
LanguageDefinition language = new LanguageDefinition();
LanguageTermDefinition languageTerm = new LanguageTermDefinition();
languageTerm.setValue(value);
language.getLanguageTerm().add(languageTerm);
mods.getModsGroup().add(language);
}
private void addPages(PartDefinition partDefinition, String value) {
if (value.contains(DOUBLE_MINUS)) {
addStartAndEndPage(value, partDefinition, DOUBLE_MINUS);
} else if (value.contains(MINUS)) {
addStartAndEndPage(value, partDefinition, MINUS);
} else {
BigInteger total = new BigInteger(value);
ExtentDefinition extent = new ExtentDefinition();
extent.setTotal(total);
partDefinition.getDetailOrExtentOrDate().add(extent);
}
}
private void addKeyWords(ModsDefinition mods, String value) {
String[] keywords = value.split(", ");
for (String keyword : keywords) {
SubjectDefinition subject = new SubjectDefinition();
StringPlusLanguagePlusAuthority topic = new StringPlusLanguagePlusAuthority();
topic.setValue(keyword);
JAXBElement<?> element = new JAXBElement<>(new QName(MODS_NAMESPACE_URI, "topic"),
StringPlusLanguagePlusAuthority.class, topic);
subject.getTopicOrGeographicOrTemporal().add(element);
mods.getModsGroup().add(subject);
}
}
private void handleAuthors(ModsDefinition mods, String value) {
String[] authors = value.split("and");
for (String author : authors) {
NameDefinition name = new NameDefinition();
name.setAtType("personal");
NamePartDefinition namePart = new NamePartDefinition();
if (author.contains(",")) {
//if author contains "," then this indicates that the author has a forename and family name
int commaIndex = author.indexOf(',');
String familyName = author.substring(0, commaIndex);
namePart.setAtType("family");
namePart.setValue(familyName);
JAXBElement<NamePartDefinition> element = new JAXBElement<>(new QName(MODS_NAMESPACE_URI, "namePart"),
NamePartDefinition.class, namePart);
name.getNamePartOrDisplayFormOrAffiliation().add(element);
//now take care of the forenames
String forename = author.substring(commaIndex + 1, author.length());
String[] forenames = forename.split(" ");
for (String given : forenames) {
if (!given.isEmpty()) {
NamePartDefinition namePartDefinition = new NamePartDefinition();
namePartDefinition.setAtType("given");
namePartDefinition.setValue(given);
element = new JAXBElement<>(new QName(MODS_NAMESPACE_URI, "namePart"), NamePartDefinition.class,
namePartDefinition);
name.getNamePartOrDisplayFormOrAffiliation().add(element);
}
}
mods.getModsGroup().add(name);
} else {
//no "," indicates that there should only be a family name
namePart.setAtType("family");
namePart.setValue(author);
JAXBElement<NamePartDefinition> element = new JAXBElement<>(new QName(MODS_NAMESPACE_URI, "namePart"),
NamePartDefinition.class, namePart);
name.getNamePartOrDisplayFormOrAffiliation().add(element);
mods.getModsGroup().add(name);
}
}
}
private void addIdentifier(String key, String value, ModsDefinition mods) {
if ("citekey".equals(key)) {
mods.setID(value);
}
IdentifierDefinition identifier = new IdentifierDefinition();
identifier.setType(key);
identifier.setValue(value);
mods.getModsGroup().add(identifier);
}
private void addStartAndEndPage(String value, PartDefinition partDefinition, String minus) {
int minusIndex = value.indexOf(minus);
String startPage = value.substring(0, minusIndex);
String endPage = "";
if (MINUS.equals(minus)) {
endPage = value.substring(minusIndex + 1);
} else if (DOUBLE_MINUS.equals(minus)) {
endPage = value.substring(minusIndex + 2);
}
StringPlusLanguage start = new StringPlusLanguage();
start.setValue(startPage);
StringPlusLanguage end = new StringPlusLanguage();
end.setValue(endPage);
ExtentDefinition extent = new ExtentDefinition();
extent.setStart(start);
extent.setEnd(end);
partDefinition.getDetailOrExtentOrDate().add(extent);
}
private void addDetail(String detailName, String value, PartDefinition partDefinition) {
DetailDefinition detail = new DetailDefinition();
StringPlusLanguage detailType = new StringPlusLanguage();
detailType.setValue(value);
detail.setType(detailName);
JAXBElement<StringPlusLanguage> element = new JAXBElement<>(new QName(MODS_NAMESPACE_URI, "number"),
StringPlusLanguage.class, detailType);
detail.getNumberOrCaptionOrTitle().add(element);
partDefinition.getDetailOrExtentOrDate().add(detail);
}
private void addOriginInformation(String key, String value, OriginInfoDefinition originInfo) {
if (FieldName.YEAR.equals(key)) {
addDate("dateIssued", value, originInfo);
} else if ("created".equals(key)) {
addDate("dateCreated", value, originInfo);
} else if ("modified".equals(key)) {
addDate("dateModified", value, originInfo);
} else if ("captured".equals(key)) {
addDate("dateCaptured", value, originInfo);
} else if (FieldName.PUBLISHER.equals(key)) {
StringPlusLanguagePlusSupplied publisher = new StringPlusLanguagePlusSupplied();
publisher.setValue(value);
JAXBElement<StringPlusLanguagePlusSupplied> element = new JAXBElement<>(
new QName(MODS_NAMESPACE_URI, "publisher"), StringPlusLanguagePlusSupplied.class, publisher);
originInfo.getPlaceOrPublisherOrDateIssued().add(element);
} else if ("issuance".equals(key)) {
IssuanceDefinition issuance = IssuanceDefinition.fromValue(value);
JAXBElement<IssuanceDefinition> element = new JAXBElement<>(new QName(MODS_NAMESPACE_URI, "issuance"),
IssuanceDefinition.class, issuance);
originInfo.getPlaceOrPublisherOrDateIssued().add(element);
} else if ("address".equals(key)) {
PlaceDefinition placeDefinition = new PlaceDefinition();
//There can be more than one place, so we split to get all places and add them
String[] places = value.split(", ");
for (String place : places) {
PlaceTermDefinition placeTerm = new PlaceTermDefinition();
//There's no possibility to see from a bib entry whether it is code or text, but since it is in the bib entry
//we assume that it is text
placeTerm.setType(CodeOrText.TEXT);
placeTerm.setValue(place);
placeDefinition.getPlaceTerm().add(placeTerm);
}
JAXBElement<PlaceDefinition> element = new JAXBElement<>(new QName(MODS_NAMESPACE_URI, "place"),
PlaceDefinition.class, placeDefinition);
originInfo.getPlaceOrPublisherOrDateIssued().add(element);
} else if ("edition".equals(key)) {
StringPlusLanguagePlusSupplied edition = new StringPlusLanguagePlusSupplied();
edition.setValue(value);
JAXBElement<StringPlusLanguagePlusSupplied> element = new JAXBElement<>(
new QName(MODS_NAMESPACE_URI, "edition"), StringPlusLanguagePlusSupplied.class, edition);
originInfo.getPlaceOrPublisherOrDateIssued().add(element);
}
}
private void addDate(String dateName, String value, OriginInfoDefinition originInfo) {
DateDefinition dateIssued = new DateDefinition();
dateIssued.setKeyDate("yes");
dateIssued.setValue(value);
JAXBElement<DateDefinition> element = new JAXBElement<>(new QName(MODS_NAMESPACE_URI, dateName),
DateDefinition.class, dateIssued);
originInfo.getPlaceOrPublisherOrDateIssued().add(element);
}
}