diff --git a/src/main/java/org/jabref/logic/exporter/MetaDataSerializer.java b/src/main/java/org/jabref/logic/exporter/MetaDataSerializer.java index 9c0e2dfe95c..cd346488677 100644 --- a/src/main/java/org/jabref/logic/exporter/MetaDataSerializer.java +++ b/src/main/java/org/jabref/logic/exporter/MetaDataSerializer.java @@ -172,4 +172,20 @@ public static String serializeCustomEntryTypes(BibEntryType entryType) { builder.append("]"); return builder.toString(); } + + public static String serializeCustomEntryTypesV2(BibEntryType entryType) { + StringBuilder builder = new StringBuilder(); + builder.append(MetaData.ENTRYTYPE_FLAG_V2); + builder.append(entryType.getType().getName()); + builder.append(": req["); + builder.append(FieldFactory.serializeOrFieldsListV2(entryType.getRequiredFields())); + builder.append("] opt["); + builder.append(FieldFactory.serializeFieldsListV2( + entryType.getOptionalFields() + .stream() + .map(BibField::field) + .collect(Collectors.toList()))); + builder.append("]"); + return builder.toString(); + } } diff --git a/src/main/java/org/jabref/logic/importer/util/MetaDataParser.java b/src/main/java/org/jabref/logic/importer/util/MetaDataParser.java index 70a17d7d5f5..aea4717f738 100644 --- a/src/main/java/org/jabref/logic/importer/util/MetaDataParser.java +++ b/src/main/java/org/jabref/logic/importer/util/MetaDataParser.java @@ -47,6 +47,9 @@ public MetaDataParser(FileUpdateMonitor fileMonitor) { public static Optional parseCustomEntryType(String comment) { String rest = comment.substring(MetaData.ENTRYTYPE_FLAG.length()); + if (comment.startsWith(MetaData.ENTRYTYPE_FLAG_V2)) { + rest = comment.substring(MetaData.ENTRYTYPE_FLAG_V2.length()); + } int indexEndOfName = rest.indexOf(':'); if (indexEndOfName < 0) { return Optional.empty(); diff --git a/src/main/java/org/jabref/model/entry/field/FieldFactory.java b/src/main/java/org/jabref/model/entry/field/FieldFactory.java index 08ae84ed852..0e27e3e156e 100644 --- a/src/main/java/org/jabref/model/entry/field/FieldFactory.java +++ b/src/main/java/org/jabref/model/entry/field/FieldFactory.java @@ -13,6 +13,7 @@ import java.util.Set; import java.util.TreeSet; import java.util.function.Predicate; +import java.util.regex.Pattern; import java.util.stream.Collectors; import org.jabref.model.entry.types.EntryType; @@ -27,6 +28,8 @@ public class FieldFactory { */ private static final String FIELD_OR_SEPARATOR = "/"; private static final String DELIMITER = ";"; + private static final String FIELD_PROPERTY_SEPARATOR = ","; + private static final String FIELD_NAME_PROPERTY_SEPARATOR = "|"; public static String serializeOrFields(Field... fields) { return serializeOrFields(new OrFields(fields)); @@ -46,10 +49,45 @@ public static String serializeOrFields(OrFields fields) { .collect(Collectors.joining(FIELD_OR_SEPARATOR)); } + public static String serializeOrFieldsV2(OrFields fields) { + return fields.getFields().stream() + .map(field -> { + if (field instanceof UnknownField unknownField) { + return serializeUnknownField(unknownField); + } else { + // In all fields known to JabRef, the name is used - JabRef knows better than the user how to case the field + return field.getName(); + } + }) + .collect(Collectors.joining(FIELD_OR_SEPARATOR)); + } + + private static String serializeUnknownField(UnknownField unknownField) { + // In case a user has put a user-defined field, the casing of that field is kept + String displayName = unknownField.getDisplayName(); + String fieldProperties = unknownField.getProperties().stream() + .map(Enum::name) + .collect(Collectors.joining(FIELD_PROPERTY_SEPARATOR)); + + if (fieldProperties.isBlank()) { + return displayName; + } + + return displayName + FIELD_NAME_PROPERTY_SEPARATOR + fieldProperties; + } + public static String serializeOrFieldsList(Set fields) { return fields.stream().map(FieldFactory::serializeOrFields).collect(Collectors.joining(DELIMITER)); } + public static String serializeOrFieldsListV2(Set fields) { + return fields.stream().map(FieldFactory::serializeOrFieldsV2).collect(Collectors.joining(DELIMITER)); + } + + public static List getNotTextFieldNames() { + return Arrays.asList(StandardField.DOI, StandardField.FILE, StandardField.URL, StandardField.URI, StandardField.ISBN, StandardField.ISSN, StandardField.MONTH, StandardField.DATE, StandardField.YEAR); + } + /** * Checks whether the given field contains LaTeX code or something else */ @@ -107,6 +145,19 @@ public static String serializeFieldsList(Collection fields) { .collect(Collectors.joining(DELIMITER)); } + public static String serializeFieldsListV2(Collection fields) { + return fields.stream() + .map(field -> { + if (field instanceof UnknownField unknownField) { + return serializeUnknownField(unknownField); + } else { + // In all fields known to JabRef, the name is used - JabRef knows better than the user how to case the field + return field.getName(); + } + }) + .collect(Collectors.joining(DELIMITER)); + } + /** * Type T is an entry type and is used to direct the mapping to the Java field class. * This somehow acts as filter, BibLaTeX "APA" entry type has field "article", but we want to have StandardField (if not explicitly requested otherwise) @@ -117,6 +168,29 @@ public static Field parseField(T type, String fieldName) { String username = fieldName.substring("comment-".length()); return new UserSpecificCommentField(username); } + + if (fieldName.contains(FIELD_NAME_PROPERTY_SEPARATOR)) { + String[] components = fieldName.split(Pattern.quote(FIELD_NAME_PROPERTY_SEPARATOR)); + + if (components.length == 2) { + String unknownFieldName = components[0]; + String[] fieldProperties = components[1].split(Pattern.quote(FIELD_PROPERTY_SEPARATOR)); + + if (fieldProperties.length == 0) { + return UnknownField.fromDisplayName(unknownFieldName); + } else if (fieldProperties.length == 1) { + return new UnknownField(unknownFieldName, unknownFieldName, FieldProperty.valueOf(fieldProperties[0])); + } else { + FieldProperty firstProperty = FieldProperty.valueOf(fieldProperties[0]); + FieldProperty[] restProperties = Arrays.stream(fieldProperties, 1, fieldProperties.length) + .map(FieldProperty::valueOf) + .toArray(FieldProperty[]::new); + + return new UnknownField(unknownFieldName, unknownFieldName, firstProperty, restProperties); + } + } + } + return OptionalUtil.orElse( OptionalUtil.orElse( OptionalUtil.orElse( diff --git a/src/main/java/org/jabref/model/metadata/MetaData.java b/src/main/java/org/jabref/model/metadata/MetaData.java index 8f7c4a1dcd2..9bdf20cb1d7 100644 --- a/src/main/java/org/jabref/model/metadata/MetaData.java +++ b/src/main/java/org/jabref/model/metadata/MetaData.java @@ -39,6 +39,7 @@ public class MetaData { public static final String META_FLAG = "jabref-meta: "; public static final String ENTRYTYPE_FLAG = "jabref-entrytype: "; + public static final String ENTRYTYPE_FLAG_V2 = "v2-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"; public static final String PREFIX_KEYPATTERN = "keypattern_"; diff --git a/src/test/java/org/jabref/logic/exporter/MetaDataSerializerTest.java b/src/test/java/org/jabref/logic/exporter/MetaDataSerializerTest.java index 4ce5342d071..5f193c5104c 100644 --- a/src/test/java/org/jabref/logic/exporter/MetaDataSerializerTest.java +++ b/src/test/java/org/jabref/logic/exporter/MetaDataSerializerTest.java @@ -17,6 +17,7 @@ import org.jabref.model.entry.BibEntryTypeBuilder; import org.jabref.model.entry.field.BibField; import org.jabref.model.entry.field.FieldPriority; +import org.jabref.model.entry.field.FieldProperty; import org.jabref.model.entry.field.OrFields; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.field.UnknownField; @@ -140,4 +141,62 @@ public static Stream serializeCustomizedEntryType() { void serializeCustomizedEntryType(BibEntryTypeBuilder bibEntryTypeBuilder, String expected) { assertEquals(expected, MetaDataSerializer.serializeCustomEntryTypes(bibEntryTypeBuilder.build())); } + + public static Stream serializeCustomizedEntryTypeV2() { + return Stream.of( + Arguments.of( + new BibEntryTypeBuilder() + .withType(new UnknownEntryType("test")) + .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE), + "v2-jabref-entrytype: test: req[author;title] opt[]" + ), + Arguments.of( + new BibEntryTypeBuilder() + .withType(new UnknownEntryType("test")) + .withRequiredFields(StandardField.AUTHOR) + .withImportantFields(StandardField.TITLE), + "v2-jabref-entrytype: test: req[author] opt[title]" + ), + Arguments.of( + new BibEntryTypeBuilder() + .withType(new UnknownEntryType("test")) + .withRequiredFields(UnknownField.fromDisplayName("Test1"), UnknownField.fromDisplayName("Test2")), + "v2-jabref-entrytype: test: req[Test1;Test2] opt[]" + ), + Arguments.of( + new BibEntryTypeBuilder() + .withType(new UnknownEntryType("test")) + .withRequiredFields(UnknownField.fromDisplayName("tEST"), UnknownField.fromDisplayName("tEsT2")), + "v2-jabref-entrytype: test: req[tEST;tEsT2] opt[]" + ), + Arguments.of( + new BibEntryTypeBuilder() + .withType(new UnknownEntryType("person")) + .withRequiredFields(new UnknownField("Name", FieldProperty.PERSON_NAMES)) + .withImportantFields( + new UnknownField("Googlescholar", FieldProperty.EXTERNAL), + new UnknownField("Orcid", FieldProperty.EXTERNAL) + ), + "v2-jabref-entrytype: person: req[Name|PERSON_NAMES] opt[Googlescholar|EXTERNAL;Orcid|EXTERNAL]" + ), + Arguments.of( + new BibEntryTypeBuilder() + .withType(new UnknownEntryType("test")) + .withRequiredFields(new UnknownField("custom1", "custom1", FieldProperty.MULTILINE_TEXT, FieldProperty.EXTERNAL)), + "v2-jabref-entrytype: test: req[custom1|EXTERNAL,MULTILINE_TEXT] opt[]" + ), + Arguments.of( + new BibEntryTypeBuilder() + .withType(new UnknownEntryType("test")) + .withRequiredFields(new UnknownField("custom2", "custom2")), + "v2-jabref-entrytype: test: req[custom2] opt[]" + ) + ); + } + + @ParameterizedTest + @MethodSource + void serializeCustomizedEntryTypeV2(BibEntryTypeBuilder bibEntryTypeBuilder, String expected) { + assertEquals(expected, MetaDataSerializer.serializeCustomEntryTypesV2(bibEntryTypeBuilder.build())); + } } diff --git a/src/test/java/org/jabref/logic/importer/util/MetaDataParserTest.java b/src/test/java/org/jabref/logic/importer/util/MetaDataParserTest.java index 73bd5e2d62c..a4a440a1fff 100644 --- a/src/test/java/org/jabref/logic/importer/util/MetaDataParserTest.java +++ b/src/test/java/org/jabref/logic/importer/util/MetaDataParserTest.java @@ -10,6 +10,7 @@ import org.jabref.logic.exporter.MetaDataSerializerTest; import org.jabref.logic.formatter.casechanger.LowerCaseFormatter; import org.jabref.model.entry.BibEntryTypeBuilder; +import org.jabref.model.entry.field.FieldProperty; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.field.UnknownField; import org.jabref.model.entry.types.UnknownEntryType; @@ -56,6 +57,53 @@ public static Stream parseCustomizedEntryType() { .withType(new UnknownEntryType("test")) .withRequiredFields(UnknownField.fromDisplayName("tEST"), UnknownField.fromDisplayName("tEsT2")), "jabref-entrytype: test: req[tEST;tEsT2] opt[]" + ), + Arguments.of( + new BibEntryTypeBuilder() + .withType(new UnknownEntryType("test")) + .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE), + "v2-jabref-entrytype: test: req[author;title] opt[]" + ), + Arguments.of( + new BibEntryTypeBuilder() + .withType(new UnknownEntryType("test")) + .withRequiredFields(StandardField.AUTHOR) + .withImportantFields(StandardField.TITLE), + "v2-jabref-entrytype: test: req[author] opt[title]" + ), + Arguments.of( + new BibEntryTypeBuilder() + .withType(new UnknownEntryType("test")) + .withRequiredFields(UnknownField.fromDisplayName("Test1"), UnknownField.fromDisplayName("Test2")), + "v2-jabref-entrytype: test: req[Test1;Test2] opt[]" + ), + Arguments.of( + new BibEntryTypeBuilder() + .withType(new UnknownEntryType("test")) + .withRequiredFields(UnknownField.fromDisplayName("tEST"), UnknownField.fromDisplayName("tEsT2")), + "v2-jabref-entrytype: test: req[tEST;tEsT2] opt[]" + ), + Arguments.of( + new BibEntryTypeBuilder() + .withType(new UnknownEntryType("person")) + .withRequiredFields(new UnknownField("Name", FieldProperty.PERSON_NAMES)) + .withImportantFields( + new UnknownField("Googlescholar", FieldProperty.EXTERNAL), + new UnknownField("Orcid", FieldProperty.EXTERNAL) + ), + "v2-jabref-entrytype: person: req[Name|PERSON_NAMES] opt[Googlescholar|EXTERNAL;Orcid|EXTERNAL]" + ), + Arguments.of( + new BibEntryTypeBuilder() + .withType(new UnknownEntryType("test")) + .withRequiredFields(new UnknownField("custom1", "custom1", FieldProperty.MULTILINE_TEXT, FieldProperty.EXTERNAL)), + "v2-jabref-entrytype: test: req[custom1|MULTILINE_TEXT,EXTERNAL] opt[]" + ), + Arguments.of( + new BibEntryTypeBuilder() + .withType(new UnknownEntryType("test")) + .withRequiredFields(new UnknownField("custom2", "custom2")), + "v2-jabref-entrytype: test: req[custom2] opt[]" ) ); }