diff --git a/.jbang/JabSrvLauncher.java b/.jbang/JabSrvLauncher.java index f7dad5872e3..353a8c75f97 100644 --- a/.jbang/JabSrvLauncher.java +++ b/.jbang/JabSrvLauncher.java @@ -19,6 +19,10 @@ //SOURCES ../jabsrv/src/main/java/org/jabref/http/server/cayw/CAYWQueryParams.java //SOURCES ../jabsrv/src/main/java/org/jabref/http/server/cayw/CAYWResource.java //SOURCES ../jabsrv/src/main/java/org/jabref/http/server/cayw/format/BibLatexFormatter.java +//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/cayw/format/NatbibFormatter.java +//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/cayw/format/MMDFormatter.java +//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/cayw/format/PandocFormatter.java +//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/cayw/format/TypstFormatter.java //SOURCES ../jabsrv/src/main/java/org/jabref/http/server/cayw/format/CAYWFormatter.java //SOURCES ../jabsrv/src/main/java/org/jabref/http/server/cayw/format/FormatterService.java //SOURCES ../jabsrv/src/main/java/org/jabref/http/server/cayw/format/SimpleJsonFormatter.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 132ad6af4bd..a2aa1dbe009 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added support for finding CSL-Styles based on their short title (e.g. apa instead of "american psychological association"). [#13728](https://github.com/JabRef/jabref/pull/13728) - We added a field for the latest ICORE conference ranking lookup on the General Tab. [#13476](https://github.com/JabRef/jabref/issues/13476) - We added BibLaTeX datamodel validation support in order to improve error message quality in entries' fields validation. [#13318](https://github.com/JabRef/jabref/issues/13318) +- We added more supported formats of CAYW endpoint of HTTP server. [#13578](https://github.com/JabRef/jabref/issues/13578) ### Changed diff --git a/jabsrv/src/main/java/org/jabref/http/server/cayw/CAYWQueryParams.java b/jabsrv/src/main/java/org/jabref/http/server/cayw/CAYWQueryParams.java index a050a4a30ae..79e2afdae04 100644 --- a/jabsrv/src/main/java/org/jabref/http/server/cayw/CAYWQueryParams.java +++ b/jabsrv/src/main/java/org/jabref/http/server/cayw/CAYWQueryParams.java @@ -22,7 +22,6 @@ public class CAYWQueryParams { private String clipboard; @QueryParam("command") - @DefaultValue("autocite") private String command; @QueryParam("minimize") @@ -46,8 +45,8 @@ public class CAYWQueryParams { @QueryParam("application") private String application; - public String getCommand() { - return command; + public Optional getCommand() { + return Optional.ofNullable(command); } public boolean isClipboard() { diff --git a/jabsrv/src/main/java/org/jabref/http/server/cayw/CAYWResource.java b/jabsrv/src/main/java/org/jabref/http/server/cayw/CAYWResource.java index dcb18e1fe6a..8e50150b6e6 100644 --- a/jabsrv/src/main/java/org/jabref/http/server/cayw/CAYWResource.java +++ b/jabsrv/src/main/java/org/jabref/http/server/cayw/CAYWResource.java @@ -74,7 +74,7 @@ public Response getCitation( if (queryParams.isProbe()) { return Response.ok("ready").build(); } - + BibDatabaseContext databaseContext = getBibDatabaseContext(queryParams); // Selected parameter handling @@ -127,7 +127,7 @@ public Response getCitation( // Push to Application parameter handling if (queryParams.getApplication().isPresent()) { - CitationCommandString citationCmd = new CitationCommandString("\\".concat(queryParams.getCommand()).concat("{"), ",", "}"); + CitationCommandString citationCmd = new CitationCommandString("\\".concat(queryParams.getCommand().orElse("autocite")).concat("{"), ",", "}"); PushToApplications.getApplication(queryParams.getApplication().get(), LOGGER::info, preferences.getPushToApplicationPreferences().withCitationCommand(citationCmd)) .ifPresent(application -> application.pushEntries(searchResults.stream().map(CAYWEntry::bibEntry).toList())); } diff --git a/jabsrv/src/main/java/org/jabref/http/server/cayw/format/BibLatexFormatter.java b/jabsrv/src/main/java/org/jabref/http/server/cayw/format/BibLatexFormatter.java index 573a7e36c2f..73c41b6236c 100644 --- a/jabsrv/src/main/java/org/jabref/http/server/cayw/format/BibLatexFormatter.java +++ b/jabsrv/src/main/java/org/jabref/http/server/cayw/format/BibLatexFormatter.java @@ -1,6 +1,7 @@ package org.jabref.http.server.cayw.format; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.jabref.http.server.cayw.CAYWQueryParams; @@ -13,9 +14,10 @@ @Service public class BibLatexFormatter implements CAYWFormatter { - @Override - public String getFormatName() { - return "biblatex"; + private final String defaultCommand; + + public BibLatexFormatter(String defaultCommand) { + this.defaultCommand = defaultCommand; } @Override @@ -25,7 +27,7 @@ public MediaType getMediaType() { @Override public String format(CAYWQueryParams queryParams, List caywEntries) { - String command = queryParams.getCommand(); + String command = queryParams.getCommand().orElse(defaultCommand); List bibEntries = caywEntries.stream() .map(CAYWEntry::bibEntry) @@ -33,7 +35,8 @@ public String format(CAYWQueryParams queryParams, List caywEntries) { return "\\%s{%s}".formatted(command, bibEntries.stream() - .map(entry -> entry.getCitationKey().orElse("")) + .map(BibEntry::getCitationKey) + .flatMap(Optional::stream) .collect(Collectors.joining(","))); } } diff --git a/jabsrv/src/main/java/org/jabref/http/server/cayw/format/CAYWFormatter.java b/jabsrv/src/main/java/org/jabref/http/server/cayw/format/CAYWFormatter.java index ed7d652a383..88a1d53a071 100644 --- a/jabsrv/src/main/java/org/jabref/http/server/cayw/format/CAYWFormatter.java +++ b/jabsrv/src/main/java/org/jabref/http/server/cayw/format/CAYWFormatter.java @@ -9,8 +9,6 @@ public interface CAYWFormatter { - String getFormatName(); - MediaType getMediaType(); String format(CAYWQueryParams caywQueryParams, List caywEntries); diff --git a/jabsrv/src/main/java/org/jabref/http/server/cayw/format/FormatterService.java b/jabsrv/src/main/java/org/jabref/http/server/cayw/format/FormatterService.java index 4cb608d6196..ae77f74a195 100644 --- a/jabsrv/src/main/java/org/jabref/http/server/cayw/format/FormatterService.java +++ b/jabsrv/src/main/java/org/jabref/http/server/cayw/format/FormatterService.java @@ -1,7 +1,6 @@ package org.jabref.http.server.cayw.format; -import java.util.HashMap; -import java.util.Map; +import java.util.Locale; import org.jabref.http.server.cayw.CAYWQueryParams; @@ -10,20 +9,28 @@ @Service public class FormatterService { - private static final String DEFAULT_FORMATTER = "biblatex"; - private final Map formatters; - - public FormatterService() { - this.formatters = new HashMap<>(); - registerFormatter(new SimpleJsonFormatter()); - registerFormatter(new BibLatexFormatter()); - } - - public void registerFormatter(CAYWFormatter formatter) { - formatters.putIfAbsent(formatter.getFormatName(), formatter); - } - public CAYWFormatter getFormatter(CAYWQueryParams queryParams) { - return formatters.getOrDefault(queryParams.getFormat().toLowerCase(), formatters.get(DEFAULT_FORMATTER)); + String format = queryParams.getFormat().toLowerCase(Locale.ROOT); + + return switch (format) { + case "natbib", + "latex", + "cite" -> + new NatbibFormatter("cite"); + case "citep" -> + new NatbibFormatter("citep"); + case "citet" -> + new NatbibFormatter("citet"); + case "mmd" -> + new MMDFormatter(); + case "pandoc" -> + new PandocFormatter(); + case "simple-json" -> + new SimpleJsonFormatter(); + case "typst" -> + new TypstFormatter(); + default -> + new BibLatexFormatter("autocite"); + }; } } diff --git a/jabsrv/src/main/java/org/jabref/http/server/cayw/format/MMDFormatter.java b/jabsrv/src/main/java/org/jabref/http/server/cayw/format/MMDFormatter.java new file mode 100644 index 00000000000..497516f4e37 --- /dev/null +++ b/jabsrv/src/main/java/org/jabref/http/server/cayw/format/MMDFormatter.java @@ -0,0 +1,31 @@ +package org.jabref.http.server.cayw.format; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.jabref.http.server.cayw.CAYWQueryParams; +import org.jabref.http.server.cayw.gui.CAYWEntry; +import org.jabref.model.entry.BibEntry; + +import jakarta.ws.rs.core.MediaType; + +public class MMDFormatter implements CAYWFormatter { + + @Override + public MediaType getMediaType() { + return MediaType.TEXT_PLAIN_TYPE; + } + + @Override + public String format(CAYWQueryParams queryParams, List caywEntries) { + List bibEntries = caywEntries.stream() + .map(CAYWEntry::bibEntry) + .toList(); + + return bibEntries.stream() + .map(entry -> entry.getCitationKey().map("[#%s][]"::formatted)) + .flatMap(Optional::stream) + .collect(Collectors.joining("")); + } +} diff --git a/jabsrv/src/main/java/org/jabref/http/server/cayw/format/NatbibFormatter.java b/jabsrv/src/main/java/org/jabref/http/server/cayw/format/NatbibFormatter.java new file mode 100644 index 00000000000..59a9947a75d --- /dev/null +++ b/jabsrv/src/main/java/org/jabref/http/server/cayw/format/NatbibFormatter.java @@ -0,0 +1,42 @@ +package org.jabref.http.server.cayw.format; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.jabref.http.server.cayw.CAYWQueryParams; +import org.jabref.http.server.cayw.gui.CAYWEntry; +import org.jabref.model.entry.BibEntry; + +import jakarta.ws.rs.core.MediaType; +import org.jvnet.hk2.annotations.Service; + +@Service +public class NatbibFormatter implements CAYWFormatter { + + private final String defaultCommand; + + public NatbibFormatter(String defaultCommand) { + this.defaultCommand = defaultCommand; + } + + @Override + public MediaType getMediaType() { + return MediaType.TEXT_PLAIN_TYPE; + } + + @Override + public String format(CAYWQueryParams queryParams, List caywEntries) { + String command = queryParams.getCommand().orElse(defaultCommand); + + List bibEntries = caywEntries.stream() + .map(CAYWEntry::bibEntry) + .toList(); + + return "\\%s{%s}".formatted(command, + bibEntries.stream() + .map(BibEntry::getCitationKey) + .flatMap(Optional::stream) + .collect(Collectors.joining(","))); + } +} diff --git a/jabsrv/src/main/java/org/jabref/http/server/cayw/format/PandocFormatter.java b/jabsrv/src/main/java/org/jabref/http/server/cayw/format/PandocFormatter.java new file mode 100644 index 00000000000..a03c0dbd14c --- /dev/null +++ b/jabsrv/src/main/java/org/jabref/http/server/cayw/format/PandocFormatter.java @@ -0,0 +1,31 @@ +package org.jabref.http.server.cayw.format; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.jabref.http.server.cayw.CAYWQueryParams; +import org.jabref.http.server.cayw.gui.CAYWEntry; +import org.jabref.model.entry.BibEntry; + +import jakarta.ws.rs.core.MediaType; + +public class PandocFormatter implements CAYWFormatter { + + @Override + public MediaType getMediaType() { + return MediaType.TEXT_PLAIN_TYPE; + } + + @Override + public String format(CAYWQueryParams queryParams, List caywEntries) { + List bibEntries = caywEntries.stream() + .map(CAYWEntry::bibEntry) + .toList(); + + return "[%s]".formatted(bibEntries.stream() + .map(entry -> entry.getCitationKey().map("@%s"::formatted)) + .flatMap(Optional::stream) + .collect(Collectors.joining("; "))); + } +} diff --git a/jabsrv/src/main/java/org/jabref/http/server/cayw/format/SimpleJsonFormatter.java b/jabsrv/src/main/java/org/jabref/http/server/cayw/format/SimpleJsonFormatter.java index 480df4becfa..a08be38c6b5 100644 --- a/jabsrv/src/main/java/org/jabref/http/server/cayw/format/SimpleJsonFormatter.java +++ b/jabsrv/src/main/java/org/jabref/http/server/cayw/format/SimpleJsonFormatter.java @@ -20,11 +20,6 @@ public SimpleJsonFormatter() { this.gson = new GsonFactory().provide(); } - @Override - public String getFormatName() { - return "simple-json"; - } - @Override public MediaType getMediaType() { return MediaType.APPLICATION_JSON_TYPE; diff --git a/jabsrv/src/main/java/org/jabref/http/server/cayw/format/TypstFormatter.java b/jabsrv/src/main/java/org/jabref/http/server/cayw/format/TypstFormatter.java new file mode 100644 index 00000000000..f455bacbaf1 --- /dev/null +++ b/jabsrv/src/main/java/org/jabref/http/server/cayw/format/TypstFormatter.java @@ -0,0 +1,37 @@ +package org.jabref.http.server.cayw.format; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.jabref.http.server.cayw.CAYWQueryParams; +import org.jabref.http.server.cayw.gui.CAYWEntry; +import org.jabref.model.entry.BibEntry; + +import jakarta.ws.rs.core.MediaType; + +public class TypstFormatter implements CAYWFormatter { + + @Override + public MediaType getMediaType() { + return MediaType.TEXT_PLAIN_TYPE; + } + + @Override + public String format(CAYWQueryParams queryParams, List caywEntries) { + List bibEntries = caywEntries.stream() + .map(CAYWEntry::bibEntry) + .toList(); + + return bibEntries.stream() + .map(entry -> entry.getCitationKey().map(key -> { + if (key.contains("/")) { + return "#cite(label(\"%s\"))".formatted(key); + } else { + return "#cite(<%s>)".formatted(key); + } + })) + .flatMap(Optional::stream) + .collect(Collectors.joining(" ")); + } +} diff --git a/jabsrv/src/test/java/org/jabref/http/server/ServerTest.java b/jabsrv/src/test/java/org/jabref/http/server/ServerTest.java index e813f597b46..c434babc077 100644 --- a/jabsrv/src/test/java/org/jabref/http/server/ServerTest.java +++ b/jabsrv/src/test/java/org/jabref/http/server/ServerTest.java @@ -3,14 +3,18 @@ import java.util.EnumSet; import java.util.List; +import javafx.collections.FXCollections; + import org.jabref.http.JabRefSrvStateManager; import org.jabref.http.SrvStateManager; import org.jabref.http.dto.GlobalExceptionMapper; import org.jabref.http.dto.GsonFactory; +import org.jabref.http.server.cayw.format.FormatterService; import org.jabref.http.server.services.FilesToServe; import org.jabref.logic.bibtex.FieldPreferences; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.preferences.LastFilesOpenedPreferences; import org.jabref.model.entry.BibEntryPreferences; import com.google.gson.Gson; @@ -32,7 +36,7 @@ * *

More information on testing with Jersey is available at the Jersey's testing documentation

. */ -abstract class ServerTest extends JerseyTest { +public abstract class ServerTest extends JerseyTest { private static CliPreferences preferences; @@ -80,6 +84,15 @@ protected void configure() { }); } + protected void addFormatterServiceToResourceConfig(ResourceConfig resourceConfig) { + resourceConfig.register(new AbstractBinder() { + @Override + protected void configure() { + bind(new FormatterService()).to(FormatterService.class); + } + }); + } + protected void addPreferencesToResourceConfig(ResourceConfig resourceConfig) { resourceConfig.register(new AbstractBinder() { @Override @@ -111,6 +124,10 @@ private static void initializePreferencesService() { FieldPreferences fieldContentFormatterPreferences = new FieldPreferences(false, List.of(), List.of()); // used twice, once for reading and once for writing when(importFormatPreferences.fieldPreferences()).thenReturn(fieldContentFormatterPreferences); + + LastFilesOpenedPreferences lastFilesOpenedPreferences = mock(LastFilesOpenedPreferences.class); + when(preferences.getLastFilesOpenedPreferences()).thenReturn(lastFilesOpenedPreferences); + when(lastFilesOpenedPreferences.getLastFilesOpened()).thenReturn(FXCollections.emptyObservableList()); } protected void addGlobalExceptionMapperToResourceConfig(ResourceConfig resourceConfig) { diff --git a/jabsrv/src/test/java/org/jabref/http/server/cayw/CAYWResourceTest.java b/jabsrv/src/test/java/org/jabref/http/server/cayw/CAYWResourceTest.java new file mode 100644 index 00000000000..0786268c341 --- /dev/null +++ b/jabsrv/src/test/java/org/jabref/http/server/cayw/CAYWResourceTest.java @@ -0,0 +1,62 @@ +package org.jabref.http.server.cayw; + +import javafx.collections.FXCollections; + +import org.jabref.http.SrvStateManager; +import org.jabref.http.server.ServerTest; +import org.jabref.model.entry.BibEntry; + +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import org.glassfish.hk2.utilities.binding.AbstractBinder; +import org.glassfish.jersey.server.ResourceConfig; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class CAYWResourceTest extends ServerTest { + @Override + protected Application configure() { + ResourceConfig resourceConfig = new ResourceConfig(CAYWResource.class); + addFilesToServeToResourceConfig(resourceConfig); + addPreferencesToResourceConfig(resourceConfig); + addGsonToResourceConfig(resourceConfig); + addFormatterServiceToResourceConfig(resourceConfig); + + resourceConfig.register(new AbstractBinder() { + @Override protected void configure() { + SrvStateManager mockSrv = Mockito.mock(SrvStateManager.class); + BibEntry bibEntry = new BibEntry().withCitationKey("Author2023test"); + Mockito.when(mockSrv.getSelectedEntries()).thenReturn(FXCollections.observableArrayList(bibEntry)); + bind(mockSrv).to(SrvStateManager.class); + } + }); + + return resourceConfig.getApplication(); + } + + @Test + void probe_returns_ready() { + Response response = target("/better-bibtex/cayw") + .queryParam("probe", "1") + .request() + .get(); + assertEquals(200, response.getStatus()); + assertEquals("ready", response.readEntity(String.class)); + assertEquals("text/plain", response.getHeaderString("Content-Type")); + } + + @Test + void biblatex() { + Response response = target("/better-bibtex/cayw") + .queryParam("format", "biblatex") + .queryParam("selected", "1") + .request() + .get(); + + assertEquals(200, response.getStatus()); + assertEquals("\\autocite{Author2023test}", response.readEntity(String.class)); + assertEquals("text/plain", response.getHeaderString("Content-Type")); + } +} diff --git a/jabsrv/src/test/java/org/jabref/http/server/cayw/format/CAYWFormattersTest.java b/jabsrv/src/test/java/org/jabref/http/server/cayw/format/CAYWFormattersTest.java new file mode 100644 index 00000000000..b879d58f469 --- /dev/null +++ b/jabsrv/src/test/java/org/jabref/http/server/cayw/format/CAYWFormattersTest.java @@ -0,0 +1,95 @@ +package org.jabref.http.server.cayw.format; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.jabref.http.server.cayw.CAYWQueryParams; +import org.jabref.http.server.cayw.gui.CAYWEntry; +import org.jabref.model.entry.BibEntry; + +import jakarta.ws.rs.core.MediaType; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class CAYWFormattersTest { + + private List caywEntries(String... keys) { + if (keys == null) { + return List.of(); + } + return Stream.of(keys) + .map(key -> new CAYWEntry(new BibEntry().withCitationKey(key), key, key, "")) + .collect(Collectors.toList()); + } + + private CAYWQueryParams queryParams(String command) { + CAYWQueryParams mock = Mockito.mock(CAYWQueryParams.class); + Mockito.when(mock.getCommand()).thenReturn(Optional.ofNullable(command)); + return mock; + } + + @Test + void biblatex_noCommand() { + BibLatexFormatter formatter = new BibLatexFormatter("autocite"); + String actual = formatter.format(queryParams(null), caywEntries("key1", "key2")); + assertEquals("\\autocite{key1,key2}", actual); + assertEquals(MediaType.TEXT_PLAIN_TYPE, formatter.getMediaType()); + } + + @Test + void biblatex_explicitCommand() { + BibLatexFormatter formatter = new BibLatexFormatter("autocite"); + String actual = formatter.format(queryParams("cite"), caywEntries("key1")); + assertEquals("\\cite{key1}", actual); + assertEquals(MediaType.TEXT_PLAIN_TYPE, formatter.getMediaType()); + } + + @Test + void biblatex_missingKey() { + BibLatexFormatter formatter = new BibLatexFormatter("autocite"); + String actual = formatter.format(queryParams(null), caywEntries("key1", "", "key3")); + assertEquals("\\autocite{key1,key3}", actual); + assertEquals(MediaType.TEXT_PLAIN_TYPE, formatter.getMediaType()); + } + + @Test + void natbib_citep() { + NatbibFormatter formatter = new NatbibFormatter("citep"); + String actual = formatter.format(queryParams(null), caywEntries("key1", "key2")); + assertEquals("\\citep{key1,key2}", actual); + assertEquals(MediaType.TEXT_PLAIN_TYPE, formatter.getMediaType()); + } + + @Test + void mmd() { + MMDFormatter formatter = new MMDFormatter(); + String actual = formatter.format(queryParams(null), caywEntries("key1", "key2")); + assertEquals("[#key1][][#key2][]", actual); + assertEquals(MediaType.TEXT_PLAIN_TYPE, formatter.getMediaType()); + } + + @Test + void pandoc() { + PandocFormatter formatter = new PandocFormatter(); + String actual = formatter.format(queryParams(null), caywEntries("key1", "key2")); + assertEquals("[@key1; @key2]", actual); + } + + @Test + void typst() { + TypstFormatter formatter = new TypstFormatter(); + String actual = formatter.format(queryParams(null), caywEntries("key1", "key2")); + assertEquals("#cite() #cite()", actual); + } + + @Test + void typst_slashInKey() { + TypstFormatter formatter = new TypstFormatter(); + String actual = formatter.format(queryParams(null), caywEntries("key1", "key2/slash", "key3")); + assertEquals("#cite() #cite(label(\"key2/slash\")) #cite()", actual); + } +}