diff --git a/jablib/src/main/java/module-info.java b/jablib/src/main/java/module-info.java index 314e34e3c31..a76c1dc6a3c 100644 --- a/jablib/src/main/java/module-info.java +++ b/jablib/src/main/java/module-info.java @@ -162,7 +162,7 @@ // region: data mapping requires jdk.xml.dom; - requires com.google.gson; + requires transitive com.google.gson; requires tools.jackson.databind; requires tools.jackson.dataformat.yaml; requires tools.jackson.core; diff --git a/jablib/src/main/java/org/jabref/logic/cleanup/SaveActionsConverter.java b/jablib/src/main/java/org/jabref/logic/cleanup/SaveActionsConverter.java new file mode 100644 index 00000000000..42c387ad8c9 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/cleanup/SaveActionsConverter.java @@ -0,0 +1,40 @@ +package org.jabref.logic.cleanup; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; + +import org.jabref.model.metadata.SaveActionsDTO; + +public class SaveActionsConverter { + public static FieldFormatterCleanupActions fromDTO(SaveActionsDTO saveActionsDTO) { + boolean enabled = saveActionsDTO.state; + StringBuilder actionsStringBuilder = new StringBuilder(); + for (Map.Entry> entry : saveActionsDTO.actions.entrySet()) { + StringJoiner joiner = new StringJoiner(","); + for (String formatter : entry.getValue()) { + joiner.add(formatter); + } + actionsStringBuilder.append(entry.getKey()) + .append("[") + .append(joiner) + .append(']'); + } + List actions = FieldFormatterCleanupMapper.parseActions(actionsStringBuilder.toString()); + return new FieldFormatterCleanupActions(enabled, actions); + } + + public static SaveActionsDTO toDTO(FieldFormatterCleanupActions saveActions) { + SaveActionsDTO saveActionsDTO = new SaveActionsDTO(); + saveActionsDTO.state = saveActions.isEnabled(); + for (FieldFormatterCleanup action : saveActions.getConfiguredActions()) { + String field = action.getField().getName(); + String formatter = action.getFormatter().getKey(); + saveActionsDTO.actions + .computeIfAbsent(field, _ -> new ArrayList<>()) + .add(formatter); + } + return saveActionsDTO; + } +} diff --git a/jablib/src/main/java/org/jabref/logic/exporter/BibDatabaseWriter.java b/jablib/src/main/java/org/jabref/logic/exporter/BibDatabaseWriter.java index cced366b611..1d822d0340a 100644 --- a/jablib/src/main/java/org/jabref/logic/exporter/BibDatabaseWriter.java +++ b/jablib/src/main/java/org/jabref/logic/exporter/BibDatabaseWriter.java @@ -30,7 +30,10 @@ import org.jabref.logic.cleanup.FieldFormatterCleanup; import org.jabref.logic.cleanup.FieldFormatterCleanupActions; import org.jabref.logic.cleanup.NormalizeWhitespacesCleanup; +import org.jabref.logic.cleanup.SaveActionsConverter; import org.jabref.logic.formatter.bibtexfields.TrimWhitespaceFormatter; +import org.jabref.logic.importer.util.SaveActionsDTOConverter; +import org.jabref.logic.os.OS; import org.jabref.logic.preferences.CliPreferences; import org.jabref.logic.util.strings.StringUtil; import org.jabref.model.FieldChange; @@ -43,9 +46,13 @@ import org.jabref.model.entry.BibtexString; import org.jabref.model.entry.field.InternalField; import org.jabref.model.metadata.MetaData; +import org.jabref.model.metadata.SaveActionsDTO; import org.jabref.model.metadata.SaveOrder; import org.jabref.model.metadata.SelfContainedSaveOrder; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; import org.jooq.lambda.Unchecked; import org.jspecify.annotations.NonNull; import org.slf4j.Logger; @@ -274,6 +281,29 @@ protected void writeMetaData(@NonNull MetaData metaData, for (Map.Entry metaItem : serializedMetaData.entrySet()) { writeMetaDataItem(metaItem); } + + writeMetaDataJson(metaData); + } + + protected void writeMetaDataJson(MetaData metaData) throws IOException { + JsonObject metaDataJson = new JsonObject(); + + if (metaData.getSaveActions().isPresent()) { + FieldFormatterCleanupActions saveActions = metaData.getSaveActions().get(); + SaveActionsDTO saveActionsDTO = SaveActionsConverter.toDTO(saveActions); + JsonObject saveActionsJson = SaveActionsDTOConverter.toJson(saveActionsDTO); + metaDataJson.add(MetaData.SAVE_ACTIONS, saveActionsJson); + } + + if (metaDataJson.isEmpty()) { + return; + } + + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + bibWriter.write(COMMENT_PREFIX + "{" + MetaData.META_FLAG_V1 + OS.NEWLINE); + bibWriter.write(gson.toJson(metaDataJson)); + bibWriter.writeLine(OS.NEWLINE + "}"); + bibWriter.finishBlock(); } protected void writeMetaDataItem(Map.Entry metaItem) throws IOException { diff --git a/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java b/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java index 100acd5512d..af9c41e8695 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java +++ b/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java @@ -26,6 +26,8 @@ import javax.xml.parsers.ParserConfigurationException; import org.jabref.logic.bibtex.FieldWriter; +import org.jabref.logic.cleanup.FieldFormatterCleanupActions; +import org.jabref.logic.cleanup.SaveActionsConverter; import org.jabref.logic.exporter.BibDatabaseWriter; import org.jabref.logic.exporter.SaveConfiguration; import org.jabref.logic.groups.GroupsFactory; @@ -35,6 +37,7 @@ import org.jabref.logic.importer.Parser; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.util.MetaDataParser; +import org.jabref.logic.importer.util.SaveActionsDTOConverter; import org.jabref.logic.l10n.Localization; import org.jabref.logic.os.OS; import org.jabref.model.database.BibDatabase; @@ -53,6 +56,7 @@ import org.jabref.model.groups.GroupHierarchyType; import org.jabref.model.groups.GroupTreeNode; import org.jabref.model.metadata.MetaData; +import org.jabref.model.metadata.SaveActionsDTO; import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.model.util.FileUpdateMonitor; @@ -60,6 +64,8 @@ import com.dd.plist.NSArray; import com.dd.plist.NSDictionary; import com.dd.plist.NSString; +import com.google.gson.Gson; +import com.google.gson.JsonObject; import io.github.adr.linked.ADR; import org.jspecify.annotations.NonNull; import org.slf4j.Logger; @@ -113,6 +119,7 @@ public class BibtexParser implements Parser { private ParserResult parserResult; private final MetaDataParser metaDataParser; + private Optional parsedJsonMetaData = Optional.empty(); private final Map parsedBibDeskGroups; private GroupTreeNode bibDeskGroupTreeNode; @@ -295,6 +302,14 @@ private ParserResult parseFileContent() throws IOException { ); } parserResult.setMetaData(metaData); + + parsedJsonMetaData.ifPresent(json -> { + if (json.has(MetaData.SAVE_ACTIONS)) { + SaveActionsDTO saveActionsDTO = SaveActionsDTOConverter.fromJson(json.getAsJsonObject(MetaData.SAVE_ACTIONS)); + FieldFormatterCleanupActions saveActions = SaveActionsConverter.fromDTO(saveActionsDTO); + metaData.setSaveActions(saveActions); + } + }); } catch (ParseException exception) { parserResult.addException(new ParserResult.Range(startLine, startColumn, line, column), exception); } @@ -401,9 +416,17 @@ private void parseJabRefComment(Map meta) { } catch (ParseException ex) { parserResult.addException(new ParserResult.Range(startLine, startColumn, line, column), ex); } + } else if (comment.startsWith(MetaData.META_FLAG_V1)) { + parsedJsonMetaData = parseCommentToJson(comment); } } + Optional parseCommentToJson(String comment) { + Gson gson = new Gson(); + String content = comment.substring(MetaData.META_FLAG_V1.length()); + return Optional.ofNullable(gson.fromJson(content, JsonObject.class)); + } + /// Adds BibDesk group entries to the JabRef database private void addBibDeskGroupEntriesToJabRefGroups() { for (String groupName : parsedBibDeskGroups.keySet()) { diff --git a/jablib/src/main/java/org/jabref/logic/importer/util/SaveActionsDTOConverter.java b/jablib/src/main/java/org/jabref/logic/importer/util/SaveActionsDTOConverter.java new file mode 100644 index 00000000000..5aa1d84812a --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/importer/util/SaveActionsDTOConverter.java @@ -0,0 +1,43 @@ +package org.jabref.logic.importer.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.jabref.model.metadata.SaveActionsDTO; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +public class SaveActionsDTOConverter { + public static SaveActionsDTO fromJson(JsonObject saveActionsJson) { + SaveActionsDTO saveActionsDTO = new SaveActionsDTO(); + saveActionsDTO.state = saveActionsJson.get("state").getAsBoolean(); + for (Map.Entry entry : saveActionsJson.entrySet()) { + // Already parsed before + if ("state".equals(entry.getKey())) { + continue; + } + List actions = new ArrayList<>(); + for (JsonElement action : entry.getValue().getAsJsonArray()) { + actions.add(action.getAsString()); + } + saveActionsDTO.actions.put(entry.getKey(), actions); + } + return saveActionsDTO; + } + + public static JsonObject toJson(SaveActionsDTO saveActionsDTO) { + JsonObject saveActionsJson = new JsonObject(); + saveActionsJson.addProperty("state", saveActionsDTO.state); + for (Map.Entry> entry : saveActionsDTO.actions.entrySet()) { + JsonArray formatters = new JsonArray(); + for (String formatter : entry.getValue()) { + formatters.add(formatter); + } + saveActionsJson.add(entry.getKey(), formatters); + } + return saveActionsJson; + } +} diff --git a/jablib/src/main/java/org/jabref/model/metadata/MetaData.java b/jablib/src/main/java/org/jabref/model/metadata/MetaData.java index 8baa4c8d88a..58212b2f4e6 100644 --- a/jablib/src/main/java/org/jabref/model/metadata/MetaData.java +++ b/jablib/src/main/java/org/jabref/model/metadata/MetaData.java @@ -38,6 +38,7 @@ public class MetaData { public static final String META_FLAG = "jabref-meta: "; + public static final String META_FLAG_V1 = "jabref-meta-0.1.0"; public static final String ENTRYTYPE_FLAG = "jabref-entrytype: "; public static final String SAVE_ORDER_CONFIG = "saveOrderConfig"; // ToDo: Rename in next major version to saveOrder, adapt testbibs public static final String SAVE_ACTIONS = "saveActions"; diff --git a/jablib/src/main/java/org/jabref/model/metadata/SaveActionsDTO.java b/jablib/src/main/java/org/jabref/model/metadata/SaveActionsDTO.java new file mode 100644 index 00000000000..6ec75ed3d73 --- /dev/null +++ b/jablib/src/main/java/org/jabref/model/metadata/SaveActionsDTO.java @@ -0,0 +1,10 @@ +package org.jabref.model.metadata; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class SaveActionsDTO { + public boolean state = false; + public Map> actions = new HashMap<>(); +} diff --git a/jablib/src/test/java/org/jabref/logic/exporter/BibDatabaseWriterTest.java b/jablib/src/test/java/org/jabref/logic/exporter/BibDatabaseWriterTest.java index 9f648255fe3..de5240a742b 100644 --- a/jablib/src/test/java/org/jabref/logic/exporter/BibDatabaseWriterTest.java +++ b/jablib/src/test/java/org/jabref/logic/exporter/BibDatabaseWriterTest.java @@ -752,13 +752,32 @@ void writeSaveActions() throws IOException { databaseWriter.writePartOfDatabase(bibtexContext, List.of()); // The order should be kept (the cleanups are a list, not a set) - assertEquals("@Comment{jabref-meta: saveActions:enabled;" - + OS.NEWLINE - + "title[lower_case]" + OS.NEWLINE - + "journal[title_case]" + OS.NEWLINE - + "day[upper_case]" + OS.NEWLINE - + ";}" - + OS.NEWLINE, stringWriter.toString()); + String expected = """ + @Comment{jabref-meta: saveActions:enabled; + title[lower_case] + journal[title_case] + day[upper_case] + ;} + + @Comment{jabref-meta-0.1.0 + { + "saveActions": { + "state": true, + "journal": [ + "title_case" + ], + "title": [ + "lower_case" + ], + "day": [ + "upper_case" + ] + } + } + } + """; + + assertEquals(expected, stringWriter.toString()); } @Test diff --git a/jablib/src/test/java/org/jabref/logic/importer/fileformat/BibtexParserTest.java b/jablib/src/test/java/org/jabref/logic/importer/fileformat/BibtexParserTest.java index 12523facc35..deb4debf4ca 100644 --- a/jablib/src/test/java/org/jabref/logic/importer/fileformat/BibtexParserTest.java +++ b/jablib/src/test/java/org/jabref/logic/importer/fileformat/BibtexParserTest.java @@ -59,6 +59,7 @@ import org.jabref.model.metadata.SaveOrder; import org.jabref.model.metadata.UserHostInfo; +import com.google.gson.JsonObject; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -1310,6 +1311,28 @@ void integrationTestSaveActions() throws IOException { saveActions.getConfiguredActions()); } + @Test + void integrationTestSaveActionsJson() throws IOException { + ParserResult parserResult = parser.parse( + Reader.of(""" + @Comment{jabref-meta-0.1.0 + { + "saveActions": { + "state": true, + "title": ["lower_case"] + } + } + } + """)); + + FieldFormatterCleanupActions saveActions = parserResult.getMetaData().getSaveActions().get(); + + assertTrue(saveActions.isEnabled()); + List expected = List.of(new FieldFormatterCleanup(StandardField.TITLE, new LowerCaseFormatter())); + List actual = saveActions.getConfiguredActions(); + assertEquals(expected, actual); + } + @Test void integrationTestBibEntryType() throws IOException { ParserResult result = parser.parse( @@ -2235,4 +2258,24 @@ void parseInvalidBibDeskFilesResultsInWarnings() throws IOException { assertEquals(List.of(firstEntry, secondEntry), result.getDatabase().getEntries()); } + + @Test + void parseCommentToJson() { + String comment = """ + jabref-meta-0.1.0 + { + "saveActions" : + { + "state": true + } + } + """; + BibtexParser parser = new BibtexParser(importFormatPreferences); + Optional actualJson = parser.parseCommentToJson(comment); + JsonObject expectedSaveActions = new JsonObject(); + expectedSaveActions.addProperty("state", true); + JsonObject expectedJson = new JsonObject(); + expectedJson.add("saveActions", expectedSaveActions); + assertEquals(Optional.of(expectedJson), actualJson); + } }