Export for academicpages 12727#15262
Conversation
Review Summary by QodoAdd academicpages export format for JabRef
WalkthroughsDescription• Implements new academicpages Jekyll template export format • Generates individual Markdown files with YAML front matter per entry • Automatically copies attached PDFs and creates .bib files • Sorts entries by type and generates IEEE-style citations Diagramflowchart LR
BibEntry["BibEntry"] -->|"extract metadata"| Markdown["Markdown File<br/>with YAML Front Matter"]
BibEntry -->|"copy PDF"| FilesDir["files/ directory"]
BibEntry -->|"generate .bib"| BibFile[".bib file"]
Markdown --> PublicationsDir["_publications/<br/>directory"]
FilesDir --> OutputDir["Output Directory"]
BibFile --> OutputDir
PublicationsDir --> OutputDir
File Changes1. jablib/src/main/java/org/jabref/logic/exporter/AcademicPagesExporter.java
|
Code Review by Qodo
1. paperurl missing without PDF
|
| buildVenue(entry).ifPresent(venue -> | ||
| sb.append("venue: '").append(escapeSingleQuotes(removeLatexEscapes(venue))).append("'\n")); | ||
| copyPdfAndGetUrl(entry, outputDir, key, fileDirForDatabase).ifPresent(paperUrl -> | ||
| sb.append("paperurl: '/files/").append(paperUrl).append("'\n")); | ||
| writeBibFile(entry, outputDir, key).ifPresent(bibUrl -> |
There was a problem hiding this comment.
1. paperurl missing without pdf 📎 Requirement gap ✓ Correctness
The exporter only writes paperurl when a PDF attachment is found, and otherwise omits the key entirely instead of falling back to DOI/URL. This can produce AcademicPages-incompatible front matter missing required fields and broken links.
Agent Prompt
## Issue description
The exporter currently writes `paperurl` only when it can copy a PDF attachment; if no PDF is attached (or attachment cannot be resolved), the `paperurl` YAML key is omitted entirely and there is no fallback to DOI/URL.
## Issue Context
AcademicPages front matter requires `paperurl` and the mapping requires choosing it from a copied adjacent attachment when available, otherwise falling back to DOI/URL.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/exporter/AcademicPagesExporter.java[184-190]
- jablib/src/main/java/org/jabref/logic/exporter/AcademicPagesExporter.java[206-228]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| /// Generates a citation string from the entry using citation style IEEE | ||
| private String buildCitation(BibEntry entry, BibDatabaseContext databaseContext) { | ||
| BibDatabaseContext context = new BibDatabaseContext(new BibDatabase(List.of(entry))); | ||
| context.setMode(databaseContext.getMode()); | ||
| String style = CSLStyleLoader.getDefaultStyle().getSource(); | ||
| return CitationStyleGenerator.generateBibliography( | ||
| List.of(entry), style, CitationStyleOutputFormat.TEXT, context, entryTypesManager) | ||
| .getFirst() | ||
| .trim(); | ||
| } |
There was a problem hiding this comment.
3. Citation not preview-based 📎 Requirement gap ✓ Correctness
The exporter generates citation via CSL default style bibliography generation rather than using JabRef’s preview rendering (or an approved short alternative). This can cause citation output to differ from JabRef preview expectations.
Agent Prompt
## Issue description
The `citation` YAML value is currently produced via CSL default bibliography generation rather than the JabRef preview renderer required by compliance.
## Issue Context
AcademicPages export is expected to produce citations consistent with JabRef preview output.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/exporter/AcademicPagesExporter.java[246-255]
- jablib/src/main/java/org/jabref/logic/exporter/AcademicPagesExporter.java[171-198]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| return CitationStyleGenerator.generateBibliography( | ||
| List.of(entry), style, CitationStyleOutputFormat.TEXT, context, entryTypesManager) | ||
| .getFirst() | ||
| .trim(); |
There was a problem hiding this comment.
4. Unsafe .getfirst() usage 📘 Rule violation ⛯ Reliability
buildCitation calls .getFirst() on the generated bibliography list without checking whether it is empty. If bibliography generation returns an empty list for any entry, export can crash at runtime.
Agent Prompt
## Issue description
The exporter uses `.getFirst()` on a list returned by citation generation without checking that the list contains an element.
## Issue Context
If citation generation fails or returns no entries for certain BibTeX data, export should not crash.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/exporter/AcademicPagesExporter.java[246-255]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
This comment has been minimized.
This comment has been minimized.
koppor
left a comment
There was a problem hiding this comment.
Good first step. Still many JabRef tooling (BbiEntry.getFieldOrAlias, FileUtil, FileUniqueness) unknown. Also unknown that SnakeYaml exists and used in JabRef. -- I commented inside.
|
|
||
| private static final String COLLECTION = "publications"; | ||
|
|
||
| private static final Comparator<BibEntry> ENTRY_TYPE_ORDER = Comparator.comparingInt( |
|
|
||
| /// Exports a JabRef library to the <a href="https://academicpages.github.io">academicpages</a> Jekyll template format. | ||
| /// Each {@link BibEntry} is written to a separate Markdown file in the given output directory. | ||
| /// The file name follows the pattern {@code YYYY-MM-DD-citationkey.md}. |
There was a problem hiding this comment.
Use Markdown
| /// The file name follows the pattern {@code YYYY-MM-DD-citationkey.md}. | |
| /// The file name follows the pattern `YYYY-MM-DD-citationkey.md`. |
|
|
||
| import org.jspecify.annotations.NonNull; | ||
|
|
||
| /// Exports a JabRef library to the <a href="https://academicpages.github.io">academicpages</a> Jekyll template format. |
There was a problem hiding this comment.
Use Markdown. Links are different in Markdown.
| } | ||
|
|
||
| /// Builds the full Markdown file content: YAML front matter + abstract body | ||
| private String buildContent(BibEntry entry, Path outputDir, BibDatabaseContext databaseContext, List<Path> fileDirForDatabase) { |
There was a problem hiding this comment.
Note: This method is the main reason why this is NOT a good first or second issue.
| } | ||
|
|
||
| /// Removes common LaTeX escape sequences for use in plain text contexts | ||
| private String removeLatexEscapes(String value) { |
There was a problem hiding this comment.
No - Use org.jabref.model.entry.BibEntry#getFieldOrAliasLatexFree when reading field content
| return entry.getField(StandardField.JOURNAL) | ||
| .or(() -> entry.getField(StandardField.JOURNALTITLE)) |
There was a problem hiding this comment.
Combine in getFieldOrAliasLatexFree
| public void export(@NonNull BibDatabaseContext databaseContext, | ||
| @NonNull Path outputFile, | ||
| @NonNull List<BibEntry> entries, | ||
| List<Path> fileDirForDatabase, |
There was a problem hiding this comment.
OMG - this parameter is wrong - it should be removed.
The constructor should get FilePreferences and store it.
Here, org.jabref.model.database.BibDatabaseContext#getFileDirectories should be used.
However, this might be too complex for now (because other classes also need to be rewritten) --> follow-up.
| assertTrue(Files.exists(generated)); | ||
|
|
||
| String content = Files.readString(generated); | ||
| assertTrue(content.contains("title: \"A Great Paper\"")); |
There was a problem hiding this comment.
Please test a complete export - not with "contains".
You can have multi line java string constants with """
You can replace placeholders before checking. Maybe {test-temp-dir} could be such a placeholder
…porter.java Co-authored-by: Oliver Kopp <kopp.dev@gmail.com>
This comment has been minimized.
This comment has been minimized.
| entry -> switch (entry.getType().getName()) { | ||
| case "book" -> | ||
| 0; | ||
| case "article" -> | ||
| 1; | ||
| case "incollection" -> | ||
| 2; | ||
| case "inproceedings" -> | ||
| 3; | ||
| default -> | ||
| 4; | ||
| } |
There was a problem hiding this comment.
Is there a possibility of more entry types being supported here in future? In that case, 0 should go to default to ensure a better backward compatibility practice.
| private String buildDate(BibEntry entry) { | ||
| String year = entry.getField(StandardField.YEAR) | ||
| .or(() -> entry.getField(StandardField.DATE) | ||
| .map(date -> date.length() >= 4 ? date.substring(0, 4) : date)) |
There was a problem hiding this comment.
This may need a bit of an explanation in a comment. Just document an example.
| return year + "-" + month + "-01"; | ||
| } | ||
|
|
||
| /// Normalizes a BibTeX month value to a two-digit string |
There was a problem hiding this comment.
Use org.jabref.model.entry.Month
| private String normalizeMonth(String month) { | ||
| return switch (month.toLowerCase(Locale.ROOT).trim()) { | ||
| case "1", | ||
| "jan", | ||
| "january" -> | ||
| "01"; | ||
| case "2", | ||
| "feb", | ||
| "february" -> | ||
| "02"; | ||
| case "3", | ||
| "mar", | ||
| "march" -> | ||
| "03"; | ||
| case "4", | ||
| "apr", | ||
| "april" -> | ||
| "04"; | ||
| case "5", | ||
| "may" -> | ||
| "05"; | ||
| case "6", | ||
| "jun", | ||
| "june" -> | ||
| "06"; | ||
| case "7", | ||
| "jul", | ||
| "july" -> | ||
| "07"; | ||
| case "8", | ||
| "aug", | ||
| "august" -> | ||
| "08"; | ||
| case "9", | ||
| "sep", | ||
| "september" -> | ||
| "09"; | ||
| case "10", | ||
| "oct", | ||
| "october" -> | ||
| "10"; | ||
| case "11", | ||
| "nov", | ||
| "november" -> | ||
| "11"; | ||
| case "12", | ||
| "dec", | ||
| "december" -> | ||
| "12"; | ||
| default -> | ||
| "01"; | ||
| }; | ||
| } |
There was a problem hiding this comment.
This should then become just:
| private String normalizeMonth(String month) { | |
| return switch (month.toLowerCase(Locale.ROOT).trim()) { | |
| case "1", | |
| "jan", | |
| "january" -> | |
| "01"; | |
| case "2", | |
| "feb", | |
| "february" -> | |
| "02"; | |
| case "3", | |
| "mar", | |
| "march" -> | |
| "03"; | |
| case "4", | |
| "apr", | |
| "april" -> | |
| "04"; | |
| case "5", | |
| "may" -> | |
| "05"; | |
| case "6", | |
| "jun", | |
| "june" -> | |
| "06"; | |
| case "7", | |
| "jul", | |
| "july" -> | |
| "07"; | |
| case "8", | |
| "aug", | |
| "august" -> | |
| "08"; | |
| case "9", | |
| "sep", | |
| "september" -> | |
| "09"; | |
| case "10", | |
| "oct", | |
| "october" -> | |
| "10"; | |
| case "11", | |
| "nov", | |
| "november" -> | |
| "11"; | |
| case "12", | |
| "dec", | |
| "december" -> | |
| "12"; | |
| default -> | |
| "01"; | |
| }; | |
| } | |
| private String normalizeMonth(String month) { | |
| return Month.parse(month) | |
| .map(Month::getTwoDigitNumber) | |
| .orElse("01"); | |
| } |
(check the indentation)
| } | ||
|
|
||
| /// Builds the full Markdown file content: YAML front matter + abstract body | ||
| private String buildContent(BibEntry entry, Path outputDir, BibDatabaseContext databaseContext, List<Path> fileDirForDatabase) { |
There was a problem hiding this comment.
Name all of these "build" methods to something more precise as they sound like void methods.
I can think of "buildAndGetContent" but open to better ideas.
| return sb.toString(); | ||
| } | ||
|
|
||
| private Optional<String> buildVenue(BibEntry entry) { |
| } | ||
|
|
||
| /// Generates a citation string from the entry using citation style IEEE | ||
| private String buildCitation(BibEntry entry, BibDatabaseContext databaseContext) { |
…5/jabref into export-for-academicpages-12727
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
✅ All tests passed ✅🏷️ Commit: 7f44514 Learn more about TestLens at testlens.app. |
* Add new export format for academicpages * exporting JabRef libraries to academicpages format * includes various test cases to validate the export functionality * Add AcademicPagesExporter for enhanced export functionality * Update jablib/src/main/java/org/jabref/logic/exporter/AcademicPagesExporter.java Co-authored-by: Oliver Kopp <kopp.dev@gmail.com> * methods for unique file name generation and enhanced date handling using TemporalAccessor * improve YAML output validation and test coverage for different entry types. Updated expected output format and added assertions for citation and file generation. * renamed methods for file name generation, content building, and venue resolution to better reflect their functionality * Add space to 12727 description * created new layout files for various academic publication types including articles, books, incollections, and inproceedings. Each layout includes fields for title, collection, excerpt, and venue. * adding mock for LayoutFormatterPreferences and updating expected YAML output format * integrate layout preferences and enhance file management * include layout preferences in constructor * Update CHANGELOG * Restore formatDate back to be clean and more readable * Refactor layout handling in AcademicPagesExporter to use TemplateExporter constants for layout prefix and extension. * Reorder the final varialbe * restore some missed files * Update academic layout files to categorize entries as 'manuscripts' and 'conferences' where applicable * improve entry type ordering and enhance date handling. Introduced a map for entry type priority and updated date resolution to return an Optional, ensuring cleaner permalink generation. * Remove test for missing month defaulting to January and add new test to verify that missing date omits date and permalink in exported files * Update CHANGELOG.md * use entrytype --------- Co-authored-by: Oliver Kopp <kopp.dev@gmail.com> Co-authored-by: Christoph <siedlerkiller@gmail.com>
* Add new export format for academicpages * exporting JabRef libraries to academicpages format * includes various test cases to validate the export functionality * Add AcademicPagesExporter for enhanced export functionality * Update jablib/src/main/java/org/jabref/logic/exporter/AcademicPagesExporter.java Co-authored-by: Oliver Kopp <kopp.dev@gmail.com> * methods for unique file name generation and enhanced date handling using TemporalAccessor * improve YAML output validation and test coverage for different entry types. Updated expected output format and added assertions for citation and file generation. * renamed methods for file name generation, content building, and venue resolution to better reflect their functionality * Add space to 12727 description * created new layout files for various academic publication types including articles, books, incollections, and inproceedings. Each layout includes fields for title, collection, excerpt, and venue. * adding mock for LayoutFormatterPreferences and updating expected YAML output format * integrate layout preferences and enhance file management * include layout preferences in constructor * Update CHANGELOG * Restore formatDate back to be clean and more readable * Refactor layout handling in AcademicPagesExporter to use TemplateExporter constants for layout prefix and extension. * Reorder the final varialbe * restore some missed files * Update academic layout files to categorize entries as 'manuscripts' and 'conferences' where applicable * improve entry type ordering and enhance date handling. Introduced a map for entry type priority and updated date resolution to return an Optional, ensuring cleaner permalink generation. * Remove test for missing month defaulting to January and add new test to verify that missing date omits date and permalink in exported files * Update CHANGELOG.md * use entrytype --------- Co-authored-by: Oliver Kopp <kopp.dev@gmail.com> Co-authored-by: Christoph <siedlerkiller@gmail.com>
* Add new export format for academicpages * exporting JabRef libraries to academicpages format * includes various test cases to validate the export functionality * Add AcademicPagesExporter for enhanced export functionality * Update jablib/src/main/java/org/jabref/logic/exporter/AcademicPagesExporter.java Co-authored-by: Oliver Kopp <kopp.dev@gmail.com> * methods for unique file name generation and enhanced date handling using TemporalAccessor * improve YAML output validation and test coverage for different entry types. Updated expected output format and added assertions for citation and file generation. * renamed methods for file name generation, content building, and venue resolution to better reflect their functionality * Add space to 12727 description * created new layout files for various academic publication types including articles, books, incollections, and inproceedings. Each layout includes fields for title, collection, excerpt, and venue. * adding mock for LayoutFormatterPreferences and updating expected YAML output format * integrate layout preferences and enhance file management * include layout preferences in constructor * Update CHANGELOG * Restore formatDate back to be clean and more readable * Refactor layout handling in AcademicPagesExporter to use TemplateExporter constants for layout prefix and extension. * Reorder the final varialbe * restore some missed files * Update academic layout files to categorize entries as 'manuscripts' and 'conferences' where applicable * improve entry type ordering and enhance date handling. Introduced a map for entry type priority and updated date resolution to return an Optional, ensuring cleaner permalink generation. * Remove test for missing month defaulting to January and add new test to verify that missing date omits date and permalink in exported files * Update CHANGELOG.md * use entrytype --------- Co-authored-by: Oliver Kopp <kopp.dev@gmail.com> Co-authored-by: Christoph <siedlerkiller@gmail.com>
Related issues and pull requests
Closes #12727
PR Description
Implements an exporter for the academicpages.
The exporter generates one Markdown file per entry with YAML front matter (title, venue, date, IEEE citation, category) and copies attached PDFs
Steps to test
1- Open library .bib
2- Select some entries
3- Click File -> Export -> Export selected entries
3- Choose Academic Pages (*.md), type a folder name, click Save, output folder contains:
Checklist
CHANGELOG.mdin a way that can be understood by the average user (if change is visible to the user)