From b45398549428a3b51f5dbc05002ea32ca21dd3ff Mon Sep 17 00:00:00 2001 From: Linus Dietz Date: Mon, 25 Nov 2024 21:10:50 +0000 Subject: [PATCH] Markdown Export Formatter (#12220) * Add Markdown export formatter * updated the changelog * fix checkstyle and l10n * fix import order * Update CHANGELOG.md Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> * Add full stop (changelog) * remove explicit save order - just for test * checkstyle * temp dir --------- Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Co-authored-by: Subhramit Basu Bhowmick Co-authored-by: Siedlerchr --- CHANGELOG.md | 1 + .../logic/exporter/ExporterFactory.java | 1 + src/main/resources/l10n/JabRef_en.properties | 2 + .../title-markdown/title-md.article.layout | 1 + .../title-markdown/title-md.book.layout | 1 + .../title-md.incollection.layout | 1 + .../title-md.inproceedings.layout | 1 + .../layout/title-markdown/title-md.layout | 1 + .../exporter/MarkdownTitleExporterTest.java | 192 ++++++++++++++++++ 9 files changed, 201 insertions(+) create mode 100644 src/main/resources/resource/layout/title-markdown/title-md.article.layout create mode 100644 src/main/resources/resource/layout/title-markdown/title-md.book.layout create mode 100644 src/main/resources/resource/layout/title-markdown/title-md.incollection.layout create mode 100644 src/main/resources/resource/layout/title-markdown/title-md.inproceedings.layout create mode 100644 src/main/resources/resource/layout/title-markdown/title-md.layout create mode 100644 src/test/java/org/jabref/logic/exporter/MarkdownTitleExporterTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index e33e6ebd9c5..b15f90e68d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Added +- We added a Markdown export layout. [#12220](https://github.com/JabRef/jabref/pull/12220) - We added a "view as BibTeX" option before importing an entry from the citation relation tab. [#11826](https://github.com/JabRef/jabref/issues/11826) - We added support finding LaTeX-encoded special characters based on plain Unicode and vice versa. [#11542](https://github.com/JabRef/jabref/pull/11542) - When a search hits a file, the file icon of that entry is changed accordingly. [#11542](https://github.com/JabRef/jabref/pull/11542) diff --git a/src/main/java/org/jabref/logic/exporter/ExporterFactory.java b/src/main/java/org/jabref/logic/exporter/ExporterFactory.java index 28a89ebfe7c..bcd6de20bef 100644 --- a/src/main/java/org/jabref/logic/exporter/ExporterFactory.java +++ b/src/main/java/org/jabref/logic/exporter/ExporterFactory.java @@ -43,6 +43,7 @@ public static ExporterFactory create(CliPreferences preferences) { exporters.add(new TemplateExporter(Localization.lang("HTML table"), "tablerefs", "tablerefs", "tablerefs", StandardFileType.HTML, layoutPreferences, saveOrder)); exporters.add(new TemplateExporter(Localization.lang("HTML list"), "listrefs", "listrefs", "listrefs", StandardFileType.HTML, layoutPreferences, saveOrder)); exporters.add(new TemplateExporter(Localization.lang("HTML table (with Abstract & BibTeX)"), "tablerefsabsbib", "tablerefsabsbib", "tablerefsabsbib", StandardFileType.HTML, layoutPreferences, saveOrder)); + exporters.add(new TemplateExporter(Localization.lang("Markdown titles"), "title-md", "title-md", "title-markdown", StandardFileType.MARKDOWN, layoutPreferences, saveOrder)); exporters.add(new TemplateExporter("Harvard RTF", "harvard", "harvard", "harvard", StandardFileType.RTF, layoutPreferences, saveOrder)); exporters.add(new TemplateExporter("ISO 690 RTF", "iso690rtf", "iso690RTF", "iso690rtf", StandardFileType.RTF, layoutPreferences, saveOrder)); exporters.add(new TemplateExporter("ISO 690", "iso690txt", "iso690", "iso690txt", StandardFileType.TXT, layoutPreferences, saveOrder)); diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index cdd2cad328a..cadce1ba583 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -466,6 +466,8 @@ The\ marked\ area\ does\ not\ contain\ any\ legible\ text!=The marked area does HTML\ table=HTML table HTML\ table\ (with\ Abstract\ &\ BibTeX)=HTML table (with Abstract & BibTeX) +Markdown\ titles=Markdown titles + Icon=Icon Ignore=Ignore diff --git a/src/main/resources/resource/layout/title-markdown/title-md.article.layout b/src/main/resources/resource/layout/title-markdown/title-md.article.layout new file mode 100644 index 00000000000..bd491638613 --- /dev/null +++ b/src/main/resources/resource/layout/title-markdown/title-md.article.layout @@ -0,0 +1 @@ +* \format[RemoveLatexCommands,HTMLChars]{\title}. \begin{journal}\format[RemoveLatexCommands,HTMLChars]{\journal}\end{journal}\begin{year} \format{\year}\end{year} diff --git a/src/main/resources/resource/layout/title-markdown/title-md.book.layout b/src/main/resources/resource/layout/title-markdown/title-md.book.layout new file mode 100644 index 00000000000..c136fa5fa3d --- /dev/null +++ b/src/main/resources/resource/layout/title-markdown/title-md.book.layout @@ -0,0 +1 @@ +* \format[RemoveLatexCommands,HTMLChars]{\title}.\begin{publisher} \format[RemoveLatexCommands,HTMLChars]{\publisher}\end{publisher} \format{\year}\end{year} diff --git a/src/main/resources/resource/layout/title-markdown/title-md.incollection.layout b/src/main/resources/resource/layout/title-markdown/title-md.incollection.layout new file mode 100644 index 00000000000..66a1528b0bc --- /dev/null +++ b/src/main/resources/resource/layout/title-markdown/title-md.incollection.layout @@ -0,0 +1 @@ +* \format[RemoveLatexCommands,HTMLChars]{\title}. \begin{booktitle}\format[RemoveLatexCommands,HTMLChars]{\booktitle}\end{booktitle}\begin{publisher}, \format[RemoveLatexCommands,HTMLChars]{\publisher}\end{publisher} \format{\year} diff --git a/src/main/resources/resource/layout/title-markdown/title-md.inproceedings.layout b/src/main/resources/resource/layout/title-markdown/title-md.inproceedings.layout new file mode 100644 index 00000000000..1d16d1eb94f --- /dev/null +++ b/src/main/resources/resource/layout/title-markdown/title-md.inproceedings.layout @@ -0,0 +1 @@ +* \format[RemoveLatexCommands,HTMLChars]{\title}. \begin{publisher}\format[RemoveLatexCommands,HTMLChars]{\publisher} \end{publisher}\begin{series}\format[RemoveLatexCommands,HTMLChars]{\series}\end{series}\begin{!series}\format[RemoveLatexCommands,HTMLChars]{\booktitle} \format{\year}\end{!series} diff --git a/src/main/resources/resource/layout/title-markdown/title-md.layout b/src/main/resources/resource/layout/title-markdown/title-md.layout new file mode 100644 index 00000000000..4fa6ad3e417 --- /dev/null +++ b/src/main/resources/resource/layout/title-markdown/title-md.layout @@ -0,0 +1 @@ +* \format[RemoveLatexCommands,HTMLChars]{\title}.\begin{year} \year\end{year} diff --git a/src/test/java/org/jabref/logic/exporter/MarkdownTitleExporterTest.java b/src/test/java/org/jabref/logic/exporter/MarkdownTitleExporterTest.java new file mode 100644 index 00000000000..19ae753ef15 --- /dev/null +++ b/src/test/java/org/jabref/logic/exporter/MarkdownTitleExporterTest.java @@ -0,0 +1,192 @@ +package org.jabref.logic.exporter; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import org.jabref.logic.layout.LayoutFormatterPreferences; +import org.jabref.logic.util.StandardFileType; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.model.metadata.SaveOrder; +import org.jabref.model.metadata.SelfContainedSaveOrder; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Answers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +class MarkdownTitleExporterTest { + + private static Exporter htmlWebsiteExporter; + private static BibDatabaseContext databaseContext; + private static final SelfContainedSaveOrder SAVE_MOST_RECENT_FIRST_SAVE_ORDER = new SelfContainedSaveOrder(SaveOrder.OrderType.SPECIFIED, List.of(new SaveOrder.SortCriterion(StandardField.YEAR, true))); + + @BeforeAll + static void setUp() { + htmlWebsiteExporter = new TemplateExporter( + "Title-Markdown", + "title-md", + "title-md", + "title-markdown", + StandardFileType.MARKDOWN, + mock(LayoutFormatterPreferences.class, Answers.RETURNS_DEEP_STUBS), + SAVE_MOST_RECENT_FIRST_SAVE_ORDER, + BlankLineBehaviour.DELETE_BLANKS); + + databaseContext = new BibDatabaseContext(); + } + + @Test + final void exportForNoEntriesWritesNothing(@TempDir Path tempDir) throws Exception { + Path file = tempDir.resolve("ThisIsARandomlyNamedFile"); + Files.createFile(file); + htmlWebsiteExporter.export(databaseContext, tempDir, Collections.emptyList()); + assertEquals(Collections.emptyList(), Files.readAllLines(file)); + } + + @Test + final void exportsCorrectContentArticle(@TempDir Path tempDir) throws Exception { + BibEntry entry = new BibEntry(StandardEntryType.Article) + .withCitationKey("test") + .withField(StandardField.AUTHOR, "Test Author") + .withField(StandardField.TITLE, "Test Title") + .withField(StandardField.JOURNAL, "Journal of this \\& that") + .withField(StandardField.PUBLISHER, "THE PRESS") + .withField(StandardField.YEAR, "2020"); + + Path file = tempDir.resolve("RandomFileName"); + Files.createFile(file); + htmlWebsiteExporter.export(databaseContext, file, Collections.singletonList(entry)); + + List expected = List.of( + "* Test Title. Journal of this & that 2020"); + + assertEquals(expected, Files.readAllLines(file)); + } + + @Test + final void exportsCorrectContentInCollection(@TempDir Path tempDir) throws Exception { + BibEntry entry = new BibEntry(StandardEntryType.InCollection) + .withCitationKey("test") + .withField(StandardField.AUTHOR, "Test Author") + .withField(StandardField.TITLE, "Test Title") + .withField(StandardField.BOOKTITLE, "Test book") + .withField(StandardField.PUBLISHER, "PRESS") + .withField(StandardField.YEAR, "2020"); + + Path file = tempDir.resolve("RandomFileName"); + Files.createFile(file); + htmlWebsiteExporter.export(databaseContext, file, Collections.singletonList(entry)); + + List expected = List.of( + "* Test Title. Test book, PRESS 2020"); + + assertEquals(expected, Files.readAllLines(file)); + } + + @Test + final void exportsCorrectContentBook(@TempDir Path tempDir) throws Exception { + BibEntry entry = new BibEntry(StandardEntryType.Book) + .withCitationKey("test") + .withField(StandardField.AUTHOR, "Test Author") + .withField(StandardField.TITLE, "Test Title") + .withField(StandardField.BOOKTITLE, "Test book") + .withField(StandardField.PUBLISHER, "PRESS") + .withField(StandardField.YEAR, "2020"); + + Path file = tempDir.resolve("RandomFileName"); + Files.createFile(file); + htmlWebsiteExporter.export(databaseContext, file, Collections.singletonList(entry)); + + List expected = List.of( + "* Test Title. PRESS 2020"); + + assertEquals(expected, Files.readAllLines(file)); + } + + @Test + final void exportsCorrectContentInProceeedingsPublisher(@TempDir Path tempDir) throws Exception { + BibEntry entry = new BibEntry(StandardEntryType.InProceedings) + .withCitationKey("test") + .withField(StandardField.AUTHOR, "Test Author") + .withField(StandardField.TITLE, "Test Title") + .withField(StandardField.BOOKTITLE, "Test Conference") + .withField(StandardField.PUBLISHER, "ACM") + .withField(StandardField.SERIES, "CONF'20") + .withField(StandardField.YEAR, "2020"); + + Path file = tempDir.resolve("RandomFileName"); + Files.createFile(file); + htmlWebsiteExporter.export(databaseContext, file, Collections.singletonList(entry)); + + List expected = List.of( + "* Test Title. ACM CONF'20"); + + assertEquals(expected, Files.readAllLines(file)); + } + + @Test + final void exportsCorrectContentInProceeedingsNoPublisher(@TempDir Path tempDir) throws Exception { + BibEntry entry = new BibEntry(StandardEntryType.InProceedings) + .withCitationKey("test") + .withField(StandardField.AUTHOR, "Test Author") + .withField(StandardField.TITLE, "Test Title") + .withField(StandardField.BOOKTITLE, "Test Conference") + .withField(StandardField.SERIES, "CONF'20") + .withField(StandardField.YEAR, "2020"); + + Path file = tempDir.resolve("RandomFileName"); + Files.createFile(file); + htmlWebsiteExporter.export(databaseContext, file, Collections.singletonList(entry)); + + List expected = List.of( + "* Test Title. CONF'20"); + + assertEquals(expected, Files.readAllLines(file)); + } + + @Test + final void exportsCorrectContentInProceeedingsNoSeries(@TempDir Path tempDir) throws Exception { + BibEntry entry = new BibEntry(StandardEntryType.InProceedings) + .withCitationKey("test") + .withField(StandardField.AUTHOR, "Test Author") + .withField(StandardField.TITLE, "Test Title") + .withField(StandardField.BOOKTITLE, "Test Conference") + .withField(StandardField.YEAR, "2020"); + + Path file = tempDir.resolve("RandomFileName"); + Files.createFile(file); + htmlWebsiteExporter.export(databaseContext, file, Collections.singletonList(entry)); + + List expected = List.of( + "* Test Title. Test Conference 2020"); + + assertEquals(expected, Files.readAllLines(file)); + } + + @Test + final void exportsCorrectContentBracketsInTitle(@TempDir Path tempDir) throws Exception { + BibEntry entry = new BibEntry(StandardEntryType.Article) + .withCitationKey("test") + .withField(StandardField.AUTHOR, "Test Author") + .withField(StandardField.TITLE, "This is {JabRef}") + .withField(StandardField.JOURNAL, "Journal of this \\& that") + .withField(StandardField.YEAR, "2020"); + + Path file = tempDir.resolve("RandomFileName"); + Files.createFile(file); + htmlWebsiteExporter.export(databaseContext, file, Collections.singletonList(entry)); + + List expected = List.of( + "* This is JabRef. Journal of this & that 2020"); + + assertEquals(expected, Files.readAllLines(file)); + } +}