-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
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.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; | ||
|
||
/** | ||
* Export format for the BibTeXML format. | ||
*/ | ||
public class BibTeXMLExportFormat extends ExportFormat { | ||
|
||
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(); | ||
|
||
bibEntry.getCiteKeyOptional().ifPresent(citeKey -> entry.setId(citeKey)); | ||
|
||
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)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrong import or why do you need There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 {@link parse()} method, but inbook needs a special treatment, because | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To link to a method you need to use the following 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(entry); | ||
for (Method method : entryMethods) { | ||
String methodWithoutSet = method.getName().replace("set", ""); | ||
String simpleClassName = entryType.getClass().getSimpleName(); | ||
|
||
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()); | ||
} | ||
|
||
} |
This file was deleted.
This file was deleted.
This file was deleted.
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()); | ||
} | ||
} |
There was a problem hiding this comment.
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.