Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EndNote XML Exporter + Rehaul Importer #11157

Merged
merged 98 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from 90 commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
1d3a3ac
Fix LICENSE link
subhramit Apr 5, 2024
316753f
Add Endnote XML Exporter
subhramit Apr 7, 2024
eed6822
Merge branch 'main' of https://github.com/JabRef/jabref into fix-for-…
subhramit Apr 7, 2024
5da187a
Fix Exporter and Importer - stage 1
subhramit Apr 7, 2024
1b211e8
Review changes-1
subhramit Apr 7, 2024
c84619e
Changelog addition
subhramit Apr 7, 2024
06a314e
Changelog line location change
subhramit Apr 7, 2024
cad0157
Merge branch 'main' of https://github.com/JabRef/jabref into fix-for-…
subhramit Apr 8, 2024
a595cdd
Merge branch 'JabRef:main' into fix-for-issue-11137
subhramit Apr 8, 2024
571e226
Review changes-2 (Majorly formatted XML)
subhramit Apr 8, 2024
ef7a69b
Merge branch 'fix-for-issue-11137' of https://github.com/subhramit/ja…
subhramit Apr 8, 2024
7108400
Merge branch 'main' into fix-for-issue-11137
subhramit Apr 8, 2024
354d514
Add test
subhramit Apr 8, 2024
d0f2a37
Merge branch 'fix-for-issue-11137' of https://github.com/subhramit/ja…
subhramit Apr 8, 2024
100ebf4
Fix single entry export test
subhramit Apr 8, 2024
07b2616
Fix ref-type in test
subhramit Apr 8, 2024
720d0ac
Fix ref-type in test
subhramit Apr 8, 2024
a0999c4
Ordered export item types
subhramit Apr 9, 2024
ba01435
Adapt test to Journal Entry type
subhramit Apr 9, 2024
1d26c65
Shift factory initialization to constructor, remove code duplicacy & …
subhramit Apr 9, 2024
a091fb7
Add fields - pagetotal, month, day, etc.
subhramit Apr 9, 2024
b252d8c
Add series/secondary title
subhramit Apr 9, 2024
3309cd9
Update test
subhramit Apr 9, 2024
f05675a
Update test - add tertiary title, fix ref no
subhramit Apr 9, 2024
2b4cab9
Review changes -3 (LinkedHashMap)
subhramit Apr 9, 2024
f56dadb
Fix LinkedHashMap implementation
subhramit Apr 9, 2024
3490c45
Merge branch 'main' into fix-for-issue-11137
subhramit Apr 9, 2024
04c9bc8
Attempt fix of LHM
subhramit Apr 9, 2024
3542169
Major rehaul: Conform to Endnote DTD, add missing fields, fix missing…
subhramit Apr 13, 2024
3b43e2a
Merge branch 'main' of https://github.com/JabRef/jabref into fix-for-…
subhramit Apr 13, 2024
4b00fd7
Fix exporter test
subhramit Apr 13, 2024
71c18e3
Preserve order of entry type mappings
subhramit Apr 13, 2024
d9361f7
minor refactor
subhramit Apr 14, 2024
05f797e
rewriteRun
subhramit Apr 14, 2024
3dead24
Fix ordering of entry types
subhramit Apr 14, 2024
899a12d
Test run success, TBD
subhramit Apr 14, 2024
73a4984
Fix exporter test
subhramit Apr 14, 2024
9df4fdb
Fix exporter test-2
subhramit Apr 14, 2024
fdf1357
Fix exporter test-3
subhramit Apr 14, 2024
1b29089
Fix exporter test-4
subhramit Apr 14, 2024
b9bc72f
Fix order of fields in exporter, adapt test(1)
subhramit Apr 14, 2024
0cfb995
Extract test resources
Siedlerchr Apr 14, 2024
ad037e3
fix merge conflicts
Siedlerchr Apr 14, 2024
1ad2189
Exporter Beta, Importer TODO!
subhramit Apr 14, 2024
83202b8
Merge branch 'fix-for-issue-11137' of https://github.com/subhramit/ja…
subhramit Apr 14, 2024
125e8a9
Exporter and Importer - final
subhramit Apr 15, 2024
58f5529
Merge branch 'main' into fix-for-issue-11137
subhramit Apr 16, 2024
4ffe0e7
Parse style children content (Fixes Importer tests)
subhramit Apr 16, 2024
39603a7
Remove grouped temporary test files
subhramit Apr 16, 2024
45a9388
Merge branch 'main' of https://github.com/JabRef/jabref into fix-for-…
subhramit Apr 16, 2024
1d17607
Merge branch 'fix-for-issue-11137' of https://github.com/subhramit/ja…
subhramit Apr 16, 2024
6f152b1
Removed debug printlns
subhramit Apr 16, 2024
d26978d
Fix exporter tests
subhramit Apr 16, 2024
5dbd152
Rename test file
subhramit Apr 16, 2024
b73dee1
Merge branch 'main' into fix-for-issue-11137
subhramit Apr 16, 2024
d7b9cc6
Fix parsing of 'related-urls'
subhramit Apr 16, 2024
e2cf097
Merge branch 'fix-for-issue-11137' of https://github.com/subhramit/ja…
subhramit Apr 16, 2024
aa6c6d3
Merge branch 'main' into fix-for-issue-11137
subhramit Apr 16, 2024
5447845
Remove citation keys from exporter test bib
subhramit Apr 16, 2024
a76b51e
Merge branch 'fix-for-issue-11137' of https://github.com/subhramit/ja…
subhramit Apr 16, 2024
f8b671d
Fix newlines in 'notes' field of importer test files
subhramit Apr 16, 2024
9b36179
Fix Title and Number overwrite
subhramit Apr 16, 2024
aa7edcf
Fix overwrite of title and number without losing info
subhramit Apr 16, 2024
8e1e7dd
Fix handling of multi-part titles & unclean titles
subhramit Apr 16, 2024
51961f4
Ad PDF suffix
subhramit Apr 16, 2024
69f7f30
Fix Importer tests
subhramit Apr 17, 2024
1eff5b9
Fix Importer tests-2
subhramit Apr 17, 2024
0b8fec4
Fix Exporter and Importer tests - trailing :PDF
subhramit Apr 17, 2024
bd87bc3
Fix Round-trip test: preceeding ':' in pdf files
subhramit Apr 17, 2024
cf37b6e
Fix typo in changelog
subhramit Apr 17, 2024
f85ce6c
Merge branch 'main' into fix-for-issue-11137
subhramit Apr 17, 2024
1732b72
More relevant changelog entry
subhramit Apr 17, 2024
c528071
Merge branch 'fix-for-issue-11137' of https://github.com/subhramit/ja…
subhramit Apr 17, 2024
74ea7e4
Use HashMap Java data type
koppor Apr 17, 2024
a78ffc4
Extract methods
koppor Apr 17, 2024
f596f3d
Extract method
koppor Apr 17, 2024
e0c18af
Restructure test
koppor Apr 17, 2024
375614b
Use "native" method for keyword list parsing
koppor Apr 17, 2024
ab70573
Fix usage of entry.getAuthorTitleYear
koppor Apr 17, 2024
61372df
Add hint to getCitationKey() usage
koppor Apr 17, 2024
a4df60b
Add some helper thing
koppor Apr 17, 2024
9b66b5e
Use author parsing
koppor Apr 17, 2024
2fe3d62
Improve (and use JOURNAL only)
koppor Apr 17, 2024
4532e28
Fix obsolete .toString()
koppor Apr 17, 2024
e0964a8
Removed commented alt-title import
subhramit Apr 17, 2024
8a081ed
Added back 'aalto-xml' comments and IS_COALESCING property
subhramit Apr 17, 2024
a3bb94d
Adapt Importer test bib's for alt-title
subhramit Apr 17, 2024
7d9763d
Add export for alt-title
subhramit Apr 17, 2024
fdb465d
Removed TODO comment (complete)
subhramit Apr 17, 2024
96b34bc
Merge branch 'main' into fix-for-issue-11137
subhramit Apr 17, 2024
eba7afd
Cleanup operation if journal matches alt-title
subhramit Apr 17, 2024
60f3ae2
Update expected bib wrt cleanup
subhramit Apr 17, 2024
07bf22c
Update expected xml titles to include secondary-titles
subhramit Apr 17, 2024
ca8e5f7
Handle case of Books/Non-article journal -> booktitle
subhramit Apr 17, 2024
08393ba
Journal (alt-periodical) check for book
subhramit Apr 17, 2024
6af33a3
Add cleanup for booktitle==alt-title
subhramit Apr 17, 2024
0527ec7
More efficient cleanup
subhramit Apr 17, 2024
794376f
Review changes - minor comments
subhramit Apr 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We added a duplicate checker for the Citation Relations tab. [#10414](https://github.com/JabRef/jabref/issues/10414)
- We added tooltip on main table cells that shows cell content or cell content and entry preview if set in preferences. [10925](https://github.com/JabRef/jabref/issues/10925)
- We added the ability to add a keyword/crossref when typing the separator character (e.g., comma) in the keywords/crossref fields. [#11178](https://github.com/JabRef/jabref/issues/11178)
- We added an exporter and improved the importer for Endnote XML format. [#11137](https://github.com/JabRef/jabref/issues/11137)

### Changed

Expand Down
302 changes: 302 additions & 0 deletions src/main/java/org/jabref/logic/exporter/EndnoteXmlExporter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
package org.jabref.logic.exporter;

import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.SequencedMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.jabref.logic.util.StandardFileType;
import org.jabref.model.database.BibDatabase;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.Author;
import org.jabref.model.entry.AuthorList;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.field.Field;
import org.jabref.model.entry.field.StandardField;
import org.jabref.model.entry.field.UnknownField;
import org.jabref.model.entry.types.EntryType;
import org.jabref.model.entry.types.IEEETranEntryType;
import org.jabref.model.entry.types.StandardEntryType;
import org.jabref.preferences.BibEntryPreferences;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class EndnoteXmlExporter extends Exporter {

private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();

private record EndNoteType(String name, Integer number) {
}

private static final Map<EntryType, EndNoteType> ENTRY_TYPE_MAPPING = new HashMap<>();

static {
ENTRY_TYPE_MAPPING.put(StandardEntryType.Article, new EndNoteType("Journal Article", 1));
ENTRY_TYPE_MAPPING.put(StandardEntryType.Book, new EndNoteType("Book", 2));
ENTRY_TYPE_MAPPING.put(StandardEntryType.InBook, new EndNoteType("Book Section", 3));
ENTRY_TYPE_MAPPING.put(StandardEntryType.InCollection, new EndNoteType("Book Section", 4));
ENTRY_TYPE_MAPPING.put(StandardEntryType.Proceedings, new EndNoteType("Conference Proceedings", 5));
ENTRY_TYPE_MAPPING.put(StandardEntryType.MastersThesis, new EndNoteType("Thesis", 6));
ENTRY_TYPE_MAPPING.put(StandardEntryType.PhdThesis, new EndNoteType("Thesis", 7));
ENTRY_TYPE_MAPPING.put(StandardEntryType.TechReport, new EndNoteType("Report", 8));
ENTRY_TYPE_MAPPING.put(StandardEntryType.Unpublished, new EndNoteType("Manuscript", 9));
ENTRY_TYPE_MAPPING.put(StandardEntryType.InProceedings, new EndNoteType("Conference Paper", 10));
ENTRY_TYPE_MAPPING.put(StandardEntryType.Conference, new EndNoteType("Conference", 11));
ENTRY_TYPE_MAPPING.put(IEEETranEntryType.Patent, new EndNoteType("Patent", 12));
ENTRY_TYPE_MAPPING.put(StandardEntryType.Online, new EndNoteType("Web Page", 13));
ENTRY_TYPE_MAPPING.put(IEEETranEntryType.Electronic, new EndNoteType("Electronic Article", 14));
ENTRY_TYPE_MAPPING.put(StandardEntryType.Misc, new EndNoteType("Generic", 15));
}

// Contains the mapping of all fields not explicitly handled by mapX methods
// We need a fixed order here, so we use a SequencedMap
private static final SequencedMap<Field, String> STANDARD_FIELD_MAPPING = new LinkedHashMap<>();

static {
STANDARD_FIELD_MAPPING.put(StandardField.PAGES, "pages");
STANDARD_FIELD_MAPPING.put(StandardField.VOLUME, "volume");
STANDARD_FIELD_MAPPING.put(StandardField.PUBLISHER, "publisher");
STANDARD_FIELD_MAPPING.put(StandardField.ISBN, "isbn");
STANDARD_FIELD_MAPPING.put(StandardField.DOI, "electronic-resource-num");
STANDARD_FIELD_MAPPING.put(StandardField.ABSTRACT, "abstract");
STANDARD_FIELD_MAPPING.put(StandardField.BOOKTITLE, "secondary-title");
STANDARD_FIELD_MAPPING.put(StandardField.EDITION, "edition");
STANDARD_FIELD_MAPPING.put(StandardField.SERIES, "tertiary-title");
STANDARD_FIELD_MAPPING.put(StandardField.NUMBER, "number");
STANDARD_FIELD_MAPPING.put(StandardField.ISSUE, "issue");
STANDARD_FIELD_MAPPING.put(StandardField.LOCATION, "pub-location");
STANDARD_FIELD_MAPPING.put(StandardField.CHAPTER, "section");
STANDARD_FIELD_MAPPING.put(StandardField.HOWPUBLISHED, "work-type");
STANDARD_FIELD_MAPPING.put(StandardField.ISSN, "issn");
STANDARD_FIELD_MAPPING.put(StandardField.ADDRESS, "auth-address");
STANDARD_FIELD_MAPPING.put(StandardField.PAGETOTAL, "page-total");
STANDARD_FIELD_MAPPING.put(StandardField.NOTE, "notes");
STANDARD_FIELD_MAPPING.put(StandardField.LABEL, "label");
STANDARD_FIELD_MAPPING.put(StandardField.LANGUAGE, "language");
STANDARD_FIELD_MAPPING.put(StandardField.KEY, "foreign-keys");
STANDARD_FIELD_MAPPING.put(new UnknownField("accession-num"), "accession-num");
}

private static final EndNoteType DEFAULT_TYPE = new EndNoteType("Generic", 15);

private final BibEntryPreferences bibEntryPreferences;

public EndnoteXmlExporter(BibEntryPreferences bibEntryPreferences) {
super("endnote", "EndNote XML", StandardFileType.XML);
this.bibEntryPreferences = bibEntryPreferences;
}

@Override
public void export(BibDatabaseContext databaseContext, Path file, List<BibEntry> entries) throws Exception {
Objects.requireNonNull(databaseContext);
Objects.requireNonNull(file);
Objects.requireNonNull(entries);

if (entries.isEmpty()) {
return;
}

DocumentBuilder dBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
Document document = dBuilder.newDocument();

Element rootElement = document.createElement("xml");
document.appendChild(rootElement);

Element recordsElement = document.createElement("records");
rootElement.appendChild(recordsElement);

for (BibEntry entry : entries) {
Element recordElement = document.createElement("record");
recordsElement.appendChild(recordElement);

mapEntryType(entry, document, recordElement);
createMetaInformationElements(databaseContext, document, recordElement);
mapAuthorAndEditor(entry, document, recordElement);
mapTitle(entry, document, recordElement);
mapJournalTitle(entry, document, recordElement);
mapKeywords(databaseContext.getDatabase(), entry, document, recordElement);
mapDates(entry, document, recordElement);
mapUrls(entry, document, recordElement);

for (Map.Entry<Field, String> fieldMapping : STANDARD_FIELD_MAPPING.entrySet()) {
Field field = fieldMapping.getKey();
String xmlElement = fieldMapping.getValue();

entry.getField(field).ifPresent(value -> {
Element fieldElement = document.createElement(xmlElement);
fieldElement.setTextContent(value);
recordElement.appendChild(fieldElement);
});
}
}

Transformer transformer = createTransformer();
DOMSource source = new DOMSource(document);
StreamResult result = new StreamResult(file.toFile());
transformer.transform(source, result);
}

private static void mapTitle(BibEntry entry, Document document, Element recordElement) {
entry.getFieldOrAlias(StandardField.TITLE).ifPresent(title -> {
Element titlesElement = document.createElement("titles");

Element titleElement = document.createElement("title");
titleElement.setTextContent(title);
titlesElement.appendChild(titleElement);

entry.getField(new UnknownField("alt-title")).ifPresent(altTitle -> {
Element altTitleElement = document.createElement("alt-title");
altTitleElement.setTextContent(altTitle);
titlesElement.appendChild(altTitleElement);
});

recordElement.appendChild(titlesElement);
});
}

private static void mapJournalTitle(BibEntry entry, Document document, Element recordElement) {
entry.getFieldOrAlias(StandardField.JOURNAL).ifPresent(journalTitle -> {
Element periodicalElement = document.createElement("periodical");
Element fullTitleElement = document.createElement("full-title");
fullTitleElement.setTextContent(journalTitle);
periodicalElement.appendChild(fullTitleElement);
recordElement.appendChild(periodicalElement);
});
}

private void mapKeywords(BibDatabase bibDatabase, BibEntry entry, Document document, Element recordElement) {
entry.getFieldOrAlias(StandardField.KEYWORDS).ifPresent(keywords -> {
Element keywordsElement = document.createElement("keywords");
entry.getResolvedKeywords(bibEntryPreferences.getKeywordSeparator(), bibDatabase).forEach(keyword -> {
Element keywordElement = document.createElement("keyword");
// Hierarchical keywords are separated by the '>' character. See {@link } for details.
keywordElement.setTextContent(keyword.get());
keywordsElement.appendChild(keywordElement);
});
recordElement.appendChild(keywordsElement);
});
}

private static void mapUrls(BibEntry entry, Document document, Element recordElement) {
Element urlsElement = document.createElement("urls");

entry.getFieldOrAlias(StandardField.FILE).ifPresent(fileField -> {
Element pdfUrlsElement = document.createElement("pdf-urls");
Element urlElement = document.createElement("url");
urlElement.setTextContent(fileField);
pdfUrlsElement.appendChild(urlElement);
urlsElement.appendChild(pdfUrlsElement);
});

entry.getFieldOrAlias(StandardField.URL).ifPresent(url -> {
Element webUrlsElement = document.createElement("web-urls");
Element urlElement = document.createElement("url");
urlElement.setTextContent(url);
webUrlsElement.appendChild(urlElement);
urlsElement.appendChild(webUrlsElement);
});

if (urlsElement.hasChildNodes()) {
recordElement.appendChild(urlsElement);
}
}

private static void mapDates(BibEntry entry, Document document, Element recordElement) {
Element datesElement = document.createElement("dates");
entry.getFieldOrAlias(StandardField.YEAR).ifPresent(year -> {
Element yearElement = document.createElement("year");
yearElement.setTextContent(year);
datesElement.appendChild(yearElement);
});
entry.getFieldOrAlias(StandardField.MONTH).ifPresent(month -> {
Element yearElement = document.createElement("month");
yearElement.setTextContent(month);
datesElement.appendChild(yearElement);
});
entry.getFieldOrAlias(StandardField.DAY).ifPresent(day -> {
Element yearElement = document.createElement("day");
yearElement.setTextContent(day);
datesElement.appendChild(yearElement);
});
// We need to use getField here - getFieldOrAlias for Date tries to convert year, month, and day to a date, which we do not want
entry.getField(StandardField.DATE).ifPresent(date -> {
Element pubDatesElement = document.createElement("pub-dates");
Element dateElement = document.createElement("date");
dateElement.setTextContent(date);
pubDatesElement.appendChild(dateElement);
datesElement.appendChild(pubDatesElement);
});
if (datesElement.hasChildNodes()) {
recordElement.appendChild(datesElement);
}
}

private static void mapEntryType(BibEntry entry, Document document, Element recordElement) {
EntryType entryType = entry.getType();
EndNoteType endNoteType = ENTRY_TYPE_MAPPING.getOrDefault(entryType, DEFAULT_TYPE);
Element refTypeElement = document.createElement("ref-type");
refTypeElement.setAttribute("name", endNoteType.name());
refTypeElement.setTextContent(endNoteType.number().toString());
recordElement.appendChild(refTypeElement);
}

private static void createMetaInformationElements(BibDatabaseContext databaseContext, Document document, Element recordElement) {
Element databaseElement = document.createElement("database");
databaseElement.setAttribute("name", "MyLibrary");
String name = databaseContext.getDatabasePath().map(Path::getFileName).map(Path::toString).orElse("MyLibrary");
databaseElement.setTextContent(name);
recordElement.appendChild(databaseElement);

Element sourceAppElement = document.createElement("source-app");
sourceAppElement.setAttribute("name", "JabRef");
sourceAppElement.setTextContent("JabRef");
recordElement.appendChild(sourceAppElement);
}

private static void mapAuthorAndEditor(BibEntry entry, Document document, Element recordElement) {
Element contributorsElement = document.createElement("contributors");
entry.getField(StandardField.AUTHOR).ifPresent(authors -> {
addPersons(authors, document, contributorsElement, "authors");
});
entry.getField(StandardField.EDITOR).ifPresent(editors -> {
addPersons(editors, document, contributorsElement, "secondary-authors");
});
if (contributorsElement.hasChildNodes()) {
recordElement.appendChild(contributorsElement);
}
}

private static void addPersons(String authors, Document document, Element contributorsElement, String wrapTagName) {
Element container = document.createElement(wrapTagName);
AuthorList parsedPersons = AuthorList.parse(authors).latexFree();
for (Author person : parsedPersons) {
Element authorElement = document.createElement("author");
authorElement.setTextContent(person.getFamilyGiven(false));
container.appendChild(authorElement);
}
contributorsElement.appendChild(container);
}

private static Transformer createTransformer() throws TransformerConfigurationException {
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, StandardCharsets.UTF_8.name());
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
return transformer;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public static ExporterFactory create(PreferencesService preferencesService,
exporters.add(new XmpPdfExporter(xmpPreferences));
exporters.add(new EmbeddedBibFilePdfExporter(bibDatabaseMode, entryTypesManager, fieldPreferences));
exporters.add(new CffExporter());
exporters.add(new EndnoteXmlExporter(preferencesService.getBibEntryPreferences()));

// Now add custom export formats
exporters.addAll(customFormats);
Expand Down
Loading
Loading