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

Rewrite Bibtexml exporter #2017

Merged
merged 5 commits into from
Sep 22, 2016
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
247 changes: 247 additions & 0 deletions src/main/java/net/sf/jabref/logic/exporter/BibTeXMLExportFormat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
package net.sf.jabref.logic.exporter;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;

import net.sf.jabref.logic.importer.fileformat.bibtexml.Article;
import net.sf.jabref.logic.importer.fileformat.bibtexml.Book;
import net.sf.jabref.logic.importer.fileformat.bibtexml.Booklet;
import net.sf.jabref.logic.importer.fileformat.bibtexml.Conference;
import net.sf.jabref.logic.importer.fileformat.bibtexml.Entry;
import net.sf.jabref.logic.importer.fileformat.bibtexml.File;
import net.sf.jabref.logic.importer.fileformat.bibtexml.Inbook;
import net.sf.jabref.logic.importer.fileformat.bibtexml.Incollection;
import net.sf.jabref.logic.importer.fileformat.bibtexml.Inproceedings;
import net.sf.jabref.logic.importer.fileformat.bibtexml.Manual;
import net.sf.jabref.logic.importer.fileformat.bibtexml.Mastersthesis;
import net.sf.jabref.logic.importer.fileformat.bibtexml.Misc;
import net.sf.jabref.logic.importer.fileformat.bibtexml.Phdthesis;
import net.sf.jabref.logic.importer.fileformat.bibtexml.Proceedings;
import net.sf.jabref.logic.importer.fileformat.bibtexml.Techreport;
import net.sf.jabref.logic.importer.fileformat.bibtexml.Unpublished;
import net.sf.jabref.model.database.BibDatabaseContext;
import net.sf.jabref.model.entry.BibEntry;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class BibTeXMLExportFormat extends ExportFormat {
Copy link
Contributor

@boceckts boceckts Sep 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a javadoc comment.


private static final String BIBTEXML_NAMESPACE_URI = "http://bibtexml.sf.net/";
private static final Locale ENGLISH = Locale.ENGLISH;
private static final Log LOGGER = LogFactory.getLog(BibTeXMLExportFormat.class);
private JAXBContext context;


public BibTeXMLExportFormat() {
super("BibTeXML", "bibtexml", null, null, ".xml");
}

@Override
public void performExport(final BibDatabaseContext databaseContext, final String resultFile, final Charset encoding,
List<BibEntry> entries) throws SaveException {
Objects.requireNonNull(databaseContext);
Objects.requireNonNull(entries);
if (entries.isEmpty()) { // Only export if entries exist
return;
}

File file = new File();
for (BibEntry bibEntry : entries) {
Entry entry = new Entry();

Optional<String> citeKey = bibEntry.getCiteKeyOptional();
if (citeKey.isPresent()) {
entry.setId(citeKey.get());
}

String type = bibEntry.getType().toLowerCase(ENGLISH);
switch (type) {
case "article":
parse(new Article(), bibEntry, entry);
break;
case "book":
parse(new Book(), bibEntry, entry);
break;
case "booklet":
parse(new Booklet(), bibEntry, entry);
break;
case "conference":
parse(new Conference(), bibEntry, entry);
break;
case "inbook":
parseInbook(new Inbook(), bibEntry, entry);
break;
case "incollection":
parse(new Incollection(), bibEntry, entry);
break;
case "inproceedings":
parse(new Inproceedings(), bibEntry, entry);
break;
case "mastersthesis":
parse(new Mastersthesis(), bibEntry, entry);
break;
case "manual":
parse(new Manual(), bibEntry, entry);
break;
case "misc":
parse(new Misc(), bibEntry, entry);
break;
case "phdthesis":
parse(new Phdthesis(), bibEntry, entry);
break;
case "proceedings":
parse(new Proceedings(), bibEntry, entry);
break;
case "techreport":
parse(new Techreport(), bibEntry, entry);
break;
case "unpublished":
parse(new Unpublished(), bibEntry, entry);
break;
default:
LOGGER.warn("unexpected type appeared");
break;
}
file.getEntry().add(entry);
}
createMarshallerAndWriteToFile(file, resultFile);
}

private void createMarshallerAndWriteToFile(File file, String resultFile) throws SaveException {
try {
if (context == null) {
context = JAXBContext.newInstance(File.class);
}
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);

marshaller.marshal(file, new java.io.File(resultFile));
Copy link
Contributor

@boceckts boceckts Sep 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong import or why do you need java.io.File here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not wrong, but there is also a class File that is generated by JAXB. Thats is why this is needed.

} catch (JAXBException e) {
throw new SaveException(e);
}
}

/**
* Contains same logic as the parse method, but inbook needs a special treatment, because
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it contains the same logic, please use a {@link} annotation.

* the contents of inbook are stored in a List of JAXBElements. So we first need to create
* a JAXBElement for every field and then add it to the content list.
*/
private void parseInbook(Inbook inbook, BibEntry bibEntry, Entry entry) {
Map<String, String> fieldMap = bibEntry.getFieldMap();
for (Map.Entry<String, String> entryField : fieldMap.entrySet()) {
String value = entryField.getValue();
String key = entryField.getKey();
if ("year".equals(key)) {
XMLGregorianCalendar calendar;
try {
calendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(value);

JAXBElement<XMLGregorianCalendar> year = new JAXBElement<>(
new QName(BIBTEXML_NAMESPACE_URI, "year"), XMLGregorianCalendar.class, calendar);
inbook.getContent().add(year);
} catch (DatatypeConfigurationException e) {
LOGGER.error("A configuration error occured");
}
} else if ("number".equals(key)) {
JAXBElement<BigInteger> number = new JAXBElement<>(new QName(BIBTEXML_NAMESPACE_URI, "number"),
BigInteger.class, new BigInteger(value));
inbook.getContent().add(number);
} else {
JAXBElement<String> element = new JAXBElement<>(new QName(BIBTEXML_NAMESPACE_URI, key), String.class,
value);
inbook.getContent().add(element);
}
}

//set the entryType to the entry
entry.setInbook(inbook);
}

/**
* Generic method that gets an instance of an entry type (article, book, booklet ...). It also
* gets one bibEntry. Then the method checks all fields of the entry and then for all fields the method
* uses the set method of the entry type with the fieldname. So for example if a bib entry has the field
* author and the value for it is "Max Mustermann" and the given type is an article, then this method
* will invoke <Code>article.setAuthor("Max Mustermann")</Code>. <br>
* <br>
* The second part of this method is that the entry type will be set to the entry. So e.g., if the type is
* article then <Code>entry.setArticle(article)</Code> will be invoked.
*
* @param entryType The type parameterized type of the entry.
* @param bibEntry The bib entry, which fields will be set to the entryType.
* @param entry The bibtexml entry. The entryType will be set to this entry.
*/
private <T> void parse(T entryType, BibEntry bibEntry, Entry entry) {
List<Method> declaredSetMethods = getListOfSetMethods(entryType);
Map<String, String> fieldMap = bibEntry.getFieldMap();
for (Map.Entry<String, String> entryField : fieldMap.entrySet()) {
String value = entryField.getValue();
String key = entryField.getKey();
for (Method method : declaredSetMethods) {
String methodNameWithoutSet = method.getName().replace("set", "").toLowerCase(ENGLISH);
try {

if ("year".equals(key) && key.equals(methodNameWithoutSet)) {
try {

XMLGregorianCalendar calendar = DatatypeFactory.newInstance()
.newXMLGregorianCalendar(value);
method.invoke(entryType, calendar);
} catch (DatatypeConfigurationException e) {
LOGGER.error("A configuration error occured");
}
break;
} else if ("number".equals(key) && key.equals(methodNameWithoutSet)) {
method.invoke(entryType, new BigInteger(value));
break;
} else if (key.equals(methodNameWithoutSet)) {
method.invoke(entryType, value);
break;
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
LOGGER.error("Could not invoke method", e);
}
}

//set the entryType to the entry
List<Method> entryMethods = getListOfSetMethods(entryType);
for (Method method : entryMethods) {
String methodWithoutSet = method.getName().replace("set", "");
String simpleClassName = entryType.getClass().getSimpleName().replaceAll("\\[", "").replaceAll("\\]",
"");
if (methodWithoutSet.equals(simpleClassName)) {
try {
method.invoke(entry, entryType);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
LOGGER.warn("Could not set the type to the entry");
}
}
}
}
}

private <T> List<Method> getListOfSetMethods(T entryType) {
return Arrays.asList(entryType.getClass().getDeclaredMethods()).stream()
.filter(method -> method.getName().startsWith("set")).collect(Collectors.toList());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static void initAllExports(Map<String, ExportFormat> customFormats,
ExportFormats.putFormat(new ExportFormat("DIN 1505", "din1505", "din1505winword", "din1505", ".rtf",
layoutPreferences, savePreferences));
ExportFormats.putFormat(
new ExportFormat("BibTeXML", "bibtexml", "bibtexml", null, ".xml", layoutPreferences, savePreferences));
new BibTeXMLExportFormat());
ExportFormats.putFormat(
new ExportFormat("BibO RDF", "bibordf", "bibordf", null, ".rdf", layoutPreferences, savePreferences));
ExportFormats.putFormat(new ExportFormat(Localization.lang("HTML table"), "tablerefs", "tablerefs", "tablerefs",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package net.sf.jabref.logic.exporter;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import net.sf.jabref.logic.importer.fileformat.BibtexImporter;
import net.sf.jabref.model.database.BibDatabaseContext;
import net.sf.jabref.model.entry.BibEntry;
import net.sf.jabref.preferences.JabRefPreferences;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import org.xmlunit.builder.Input;
import org.xmlunit.builder.Input.Builder;
import org.xmlunit.diff.DefaultNodeMatcher;
import org.xmlunit.diff.ElementSelectors;
import org.xmlunit.matchers.CompareMatcher;


@RunWith(Parameterized.class)
public class BibTeXMLExporterTestFiles {

public BibDatabaseContext databaseContext;
public Charset charset;
public File tempFile;
public BibTeXMLExportFormat bibtexmlExportFormat;
public BibtexImporter testImporter;

@Parameter
public String filename;
public Path resourceDir;

@Rule
public TemporaryFolder testFolder = new TemporaryFolder();


@Parameters(name = "{0}")
public static Collection<String> fileNames() throws IOException, URISyntaxException {
try (Stream<Path> stream = Files.list(Paths.get(BibTeXMLExporterTestFiles.class.getResource("").toURI()))) {
return stream.map(n -> n.getFileName().toString()).filter(n -> n.endsWith(".bib"))
.filter(n -> n.startsWith("BibTeXML")).collect(Collectors.toList());
}
}

@Before
public void setUp() throws Exception {
resourceDir = Paths.get(BibTeXMLExporterTestFiles.class.getResource("").toURI());
databaseContext = new BibDatabaseContext();
charset = StandardCharsets.UTF_8;
bibtexmlExportFormat = new BibTeXMLExportFormat();
tempFile = testFolder.newFile();
testImporter = new BibtexImporter(JabRefPreferences.getInstance().getImportFormatPreferences());
}

@Test
public final void testPerformExport() throws IOException, SaveException {
String xmlFileName = filename.replace(".bib", ".xml");
Path importFile = resourceDir.resolve(filename);
String tempFilename = tempFile.getCanonicalPath();

List<BibEntry> entries = testImporter.importDatabase(importFile, StandardCharsets.UTF_8).getDatabase()
.getEntries();

bibtexmlExportFormat.performExport(databaseContext, tempFile.getPath(), charset, entries);

Builder control = Input.from(Files.newInputStream(resourceDir.resolve(xmlFileName)));
Builder test = Input.from(Files.newInputStream(Paths.get(tempFilename)));

Assert.assertThat(test, CompareMatcher.isSimilarTo(control)
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText)).throwComparisonFailure());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
% Encoding: UTF-8

@Article{Mustermann2016,
author = {Max Mustermann},
title = {Java tricks},
journal = {Java Journal},
year = {2016},
pages = {2},
month = {February},
keywords = {java}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<file xmlns="http://bibtexml.sf.net/">
<entry id="Mustermann2016">
<article>
<author>Max Mustermann</author>
<title>Java tricks</title>
<journal>Java Journal</journal>
<year>2016</year>
<pages>2</pages>
<month>February</month>
<keywords>java</keywords>
</article>
</entry>
</file>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
% Encoding: UTF-8

@Article{,
author = {Max Mustermann},
title = {Java tricks},
journal = {Java Journal},
year = {2016},
pages = {2},
month = {February},
keywords = {java}
}
Loading