diff --git a/docs/adr/0008-use-public-final-instead-of-getters.md b/docs/adr/0008-use-public-final-instead-of-getters.md new file mode 100644 index 00000000000..daf781194ab --- /dev/null +++ b/docs/adr/0008-use-public-final-instead-of-getters.md @@ -0,0 +1,22 @@ +# Use `public final` instead of getters to offer access to immutable variables + +## Context and Problem Statement + +When making immutable data accessible in a java class, should it be using getters or by non-modifiable fields? + +## Considered Options + +* Offer public static field +* Offer getters + +## Decision Outcome + +Chosen option: "Offer public static field", because getters used to be a convention which was even more manifested due to libraries depending on the existence on getters/setters. In the case of immutable variables, adding public getters is just useless since one is not hiding anything. + +### Positive Consequences + +* Shorter code + +### Negative Consequences + +* new comers could get confused, because getters/setters are still teached diff --git a/src/main/java/org/jabref/gui/DragAndDropDataFormats.java b/src/main/java/org/jabref/gui/DragAndDropDataFormats.java index 67aa1cefce0..8b658a1655e 100644 --- a/src/main/java/org/jabref/gui/DragAndDropDataFormats.java +++ b/src/main/java/org/jabref/gui/DragAndDropDataFormats.java @@ -4,7 +4,7 @@ import javafx.scene.input.DataFormat; -import org.jabref.logic.citationstyle.PreviewLayout; +import org.jabref.logic.preview.PreviewLayout; /** * Contains all the different {@link DataFormat}s that may occur in JabRef. diff --git a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index 359569fbfd4..58e5088175e 100644 --- a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -23,7 +23,7 @@ import org.jabref.gui.specialfields.SpecialFieldMenuItemFactory; import org.jabref.logic.citationstyle.CitationStyleOutputFormat; import org.jabref.logic.citationstyle.CitationStylePreviewLayout; -import org.jabref.logic.citationstyle.PreviewLayout; +import org.jabref.logic.preview.PreviewLayout; import org.jabref.model.entry.field.SpecialField; import org.jabref.preferences.JabRefPreferences; import org.jabref.preferences.PreferencesService; diff --git a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java index 3ac1ba09e55..d4fac5b02ce 100644 --- a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java +++ b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java @@ -18,8 +18,8 @@ import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.ValueTableCellFactory; import org.jabref.gui.util.ViewModelTableRowFactory; -import org.jabref.logic.citationstyle.TextBasedPreviewLayout; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.layout.TextBasedPreviewLayout; import org.jabref.logic.openoffice.OOBibStyle; import org.jabref.logic.openoffice.StyleLoader; import org.jabref.logic.util.TestEntry; diff --git a/src/main/java/org/jabref/gui/preferences/PreviewTabView.java b/src/main/java/org/jabref/gui/preferences/PreviewTabView.java index c366341c729..8d387006ce7 100644 --- a/src/main/java/org/jabref/gui/preferences/PreviewTabView.java +++ b/src/main/java/org/jabref/gui/preferences/PreviewTabView.java @@ -29,8 +29,8 @@ import org.jabref.gui.preview.PreviewViewer; import org.jabref.gui.util.IconValidationDecorator; import org.jabref.gui.util.ViewModelListCellFactory; -import org.jabref.logic.citationstyle.PreviewLayout; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.preview.PreviewLayout; import org.jabref.logic.util.TestEntry; import org.jabref.model.database.BibDatabaseContext; import org.jabref.preferences.JabRefPreferences; diff --git a/src/main/java/org/jabref/gui/preferences/PreviewTabViewModel.java b/src/main/java/org/jabref/gui/preferences/PreviewTabViewModel.java index 08a7b0c18d3..1ff3f88a73b 100644 --- a/src/main/java/org/jabref/gui/preferences/PreviewTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/PreviewTabViewModel.java @@ -34,9 +34,9 @@ import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.citationstyle.CitationStyle; import org.jabref.logic.citationstyle.CitationStylePreviewLayout; -import org.jabref.logic.citationstyle.PreviewLayout; -import org.jabref.logic.citationstyle.TextBasedPreviewLayout; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.layout.TextBasedPreviewLayout; +import org.jabref.logic.preview.PreviewLayout; import org.jabref.preferences.JabRefPreferences; import org.jabref.preferences.PreviewPreferences; diff --git a/src/main/java/org/jabref/gui/preview/CopyCitationAction.java b/src/main/java/org/jabref/gui/preview/CopyCitationAction.java index 46e671147c2..ce7cbba25d6 100644 --- a/src/main/java/org/jabref/gui/preview/CopyCitationAction.java +++ b/src/main/java/org/jabref/gui/preview/CopyCitationAction.java @@ -18,11 +18,11 @@ import org.jabref.logic.citationstyle.CitationStyleGenerator; import org.jabref.logic.citationstyle.CitationStyleOutputFormat; import org.jabref.logic.citationstyle.CitationStylePreviewLayout; -import org.jabref.logic.citationstyle.PreviewLayout; import org.jabref.logic.l10n.Localization; import org.jabref.logic.layout.Layout; import org.jabref.logic.layout.LayoutFormatterPreferences; import org.jabref.logic.layout.LayoutHelper; +import org.jabref.logic.preview.PreviewLayout; import org.jabref.logic.util.OS; import org.jabref.model.entry.BibEntry; import org.jabref.preferences.PreviewPreferences; diff --git a/src/main/java/org/jabref/gui/preview/PreviewPanel.java b/src/main/java/org/jabref/gui/preview/PreviewPanel.java index 941818ae8ef..3c8f9b69c6f 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewPanel.java +++ b/src/main/java/org/jabref/gui/preview/PreviewPanel.java @@ -23,8 +23,8 @@ import org.jabref.gui.icon.IconTheme; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.keyboard.KeyBindingRepository; -import org.jabref.logic.citationstyle.PreviewLayout; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.preview.PreviewLayout; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.preferences.JabRefPreferences; diff --git a/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/src/main/java/org/jabref/gui/preview/PreviewViewer.java index 6a0c42b0837..2ac50ebee2b 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewViewer.java +++ b/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -23,9 +23,9 @@ import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.TaskExecutor; import org.jabref.gui.util.ThemeLoader; -import org.jabref.logic.citationstyle.PreviewLayout; import org.jabref.logic.exporter.ExporterFactory; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.preview.PreviewLayout; import org.jabref.logic.search.SearchQuery; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; diff --git a/src/main/java/org/jabref/gui/util/CustomLocalDragboard.java b/src/main/java/org/jabref/gui/util/CustomLocalDragboard.java index a36ec2cb969..3ba1b83de10 100644 --- a/src/main/java/org/jabref/gui/util/CustomLocalDragboard.java +++ b/src/main/java/org/jabref/gui/util/CustomLocalDragboard.java @@ -7,7 +7,7 @@ import org.jabref.gui.DragAndDropDataFormats; import org.jabref.gui.StateManager; -import org.jabref.logic.citationstyle.PreviewLayout; +import org.jabref.logic.preview.PreviewLayout; import org.jabref.model.entry.BibEntry; /** diff --git a/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java b/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java index a5be765f4fe..257777abb69 100644 --- a/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java +++ b/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java @@ -87,11 +87,7 @@ public void writeWithoutPrependedNewlines(BibEntry entry, Writer out, BibDatabas } /** - * Write fields in the order of requiredFields, optionalFields and other fields, but does not sort the fields. - * - * @param entry - * @param out - * @throws IOException + * Writes fields in the order of requiredFields, optionalFields and other fields, but does not sort the fields. */ private void writeRequiredFieldsFirstRemainingFieldsSecond(BibEntry entry, Writer out, BibDatabaseMode bibDatabaseMode) throws IOException { diff --git a/src/main/java/org/jabref/logic/bibtex/FieldContentFormatter.java b/src/main/java/org/jabref/logic/bibtex/FieldContentFormatter.java index 5b2a2c6171d..183f174608c 100644 --- a/src/main/java/org/jabref/logic/bibtex/FieldContentFormatter.java +++ b/src/main/java/org/jabref/logic/bibtex/FieldContentFormatter.java @@ -43,7 +43,6 @@ public FieldContentFormatter(FieldContentFormatterPreferences preferences) { */ public String format(String fieldContent, Field field) { if (multiLineFields.contains(field)) { - // Unify line breaks return StringUtil.unifyLineBreaks(fieldContent, OS.NEWLINE); } diff --git a/src/main/java/org/jabref/logic/bibtex/FieldContentFormatterPreferences.java b/src/main/java/org/jabref/logic/bibtex/FieldContentFormatterPreferences.java index 26f13248b85..02df6c020c0 100644 --- a/src/main/java/org/jabref/logic/bibtex/FieldContentFormatterPreferences.java +++ b/src/main/java/org/jabref/logic/bibtex/FieldContentFormatterPreferences.java @@ -9,8 +9,10 @@ public class FieldContentFormatterPreferences { private final List nonWrappableFields; + /** + * Constructor defining that there are not any non-wrappable fields + */ public FieldContentFormatterPreferences() { - // This constructor is only to allow an empty constructor in SavePreferences this.nonWrappableFields = Collections.emptyList(); } diff --git a/src/main/java/org/jabref/logic/bibtex/FieldWriter.java b/src/main/java/org/jabref/logic/bibtex/FieldWriter.java index e0ddfc9ace0..c7ce8ea8a10 100644 --- a/src/main/java/org/jabref/logic/bibtex/FieldWriter.java +++ b/src/main/java/org/jabref/logic/bibtex/FieldWriter.java @@ -6,20 +6,22 @@ import org.jabref.model.entry.field.StandardField; import org.jabref.model.strings.StringUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** - * Obeys following settings: - * * JabRefPreferences.RESOLVE_STRINGS_ALL_FIELDS - * * JabRefPreferences.DO_NOT_RESOLVE_STRINGS_FOR - * * JabRefPreferences.WRITEFIELD_WRAPFIELD + * Converts JabRef's internal BibTeX representation of a BibTeX field to BibTeX text representation */ public class FieldWriter { + private static final Logger LOGGER = LoggerFactory.getLogger(FieldWriter.class); + private static final char FIELD_START = '{'; private static final char FIELD_END = '}'; + private final boolean neverFailOnHashes; private final FieldWriterPreferences preferences; private final FieldContentFormatter formatter; - private StringBuilder stringBuilder; public FieldWriter(FieldWriterPreferences preferences) { this(true, preferences); @@ -58,12 +60,15 @@ private static void checkBraces(String text) throws InvalidFieldValueException { // Then we throw an exception if the error criteria are met. if (!(right == 0) && (left == 0)) { + LOGGER.error("Unescaped '}' character without opening bracket ends string prematurely. Field value: {}", text ); throw new InvalidFieldValueException("Unescaped '}' character without opening bracket ends string prematurely. Field value: " + text); } if (!(right == 0) && (right < left)) { + LOGGER.error("Unescaped '}' character without opening bracket ends string prematurely. Field value: {}", text); throw new InvalidFieldValueException("Unescaped '}' character without opening bracket ends string prematurely. Field value: " + text); } if (left != right) { + LOGGER.error("Braces don't match. Field value: {}", text); throw new InvalidFieldValueException("Braces don't match. Field value: " + text); } } @@ -95,9 +100,10 @@ public String write(Field field, String content) throws InvalidFieldValueExcepti * For instance, #jan# - #feb# gets jan #{ - } # feb (see @link{org.jabref.logic.bibtex.LatexFieldFormatterTests#makeHashEnclosedWordsRealStringsInMonthField()}) */ private String formatAndResolveStrings(String content, Field field) throws InvalidFieldValueException { - stringBuilder = new StringBuilder(); checkBraces(content); + StringBuilder stringBuilder = new StringBuilder(); + // Here we assume that the user encloses any bibtex strings in #, e.g.: // #jan# - #feb# // ...which will be written to the file like this: @@ -126,6 +132,9 @@ private String formatAndResolveStrings(String content, Field field) throws Inval if (neverFailOnHashes) { pos1 = content.length(); // just write out the rest of the text, and throw no exception } else { + LOGGER.error("The # character is not allowed in BibTeX strings unless escaped as in '\\#'. " + + "In JabRef, use pairs of # characters to indicate a string. " + + "Note that the entry causing the problem has been selected. Field value: {}", content); throw new InvalidFieldValueException( "The # character is not allowed in BibTeX strings unless escaped as in '\\#'.\n" + "In JabRef, use pairs of # characters to indicate a string.\n" @@ -135,13 +144,13 @@ private String formatAndResolveStrings(String content, Field field) throws Inval } if (pos1 > pivot) { - writeText(content, pivot, pos1); + writeText(stringBuilder, content, pivot, pos1); } if ((pos1 < content.length()) && ((pos2 - 1) > pos1)) { // We check that the string label is not empty. That means // an occurrence of ## will simply be ignored. Should it instead // cause an error message? - writeStringLabel(content, pos1 + 1, pos2, pos1 == pivot, + writeStringLabel(stringBuilder, content, pos1 + 1, pos2, pos1 == pivot, (pos2 + 1) == content.length()); } @@ -168,28 +177,33 @@ private boolean shouldResolveStrings(Field field) { private String formatWithoutResolvingStrings(String content, Field field) throws InvalidFieldValueException { checkBraces(content); - stringBuilder = new StringBuilder(String.valueOf(FIELD_START)); - + StringBuilder stringBuilder = new StringBuilder(String.valueOf(FIELD_START)); stringBuilder.append(formatter.format(content, field)); - stringBuilder.append(FIELD_END); - return stringBuilder.toString(); } - private void writeText(String text, int startPos, int endPos) { + /** + * @param stringBuilder the StringBuilder to append the text to + * @param text the text to append + */ + private void writeText(StringBuilder stringBuilder, String text, int startPos, int endPos) { stringBuilder.append(FIELD_START); stringBuilder.append(text, startPos, endPos); stringBuilder.append(FIELD_END); } - private void writeStringLabel(String text, int startPos, int endPos, boolean first, boolean last) { - putIn((first ? "" : " # ") + text.substring(startPos, endPos) - + (last ? "" : " # ")); - } - - private void putIn(String s) { - stringBuilder.append(StringUtil.wrap(s, preferences.getLineLength(), OS.NEWLINE)); + /** + * @param stringBuilder the StringBuilder to append the text to + * @param text the text use as basis to get the text to append + * @param startPos the position in text where the text to add starts + * @param endPos the position in text where the text to add ends + * @param isFirst true if the label to write is the first one to write + * @param isLast true if the label to write is the last one to write + */ + private void writeStringLabel(StringBuilder stringBuilder, String text, int startPos, int endPos, boolean isFirst, boolean isLast) { + String line = (isFirst ? "" : " # ") + text.substring(startPos, endPos) + (isLast ? "" : " # "); + String wrappedLine = StringUtil.wrap(line, preferences.getLineLength(), OS.NEWLINE); + stringBuilder.append(wrappedLine); } - } diff --git a/src/main/java/org/jabref/logic/bibtex/FieldWriterPreferences.java b/src/main/java/org/jabref/logic/bibtex/FieldWriterPreferences.java index 5caaa76de1b..51654c788a8 100644 --- a/src/main/java/org/jabref/logic/bibtex/FieldWriterPreferences.java +++ b/src/main/java/org/jabref/logic/bibtex/FieldWriterPreferences.java @@ -19,6 +19,10 @@ public FieldWriterPreferences(boolean resolveStringsAllFields, List doNot this.fieldContentFormatterPreferences = fieldContentFormatterPreferences; } + /** + * Creates an instance with default values (not obeying any user preferences). This constructor should be used with + * caution. The other constructor has to be preferred. + */ public FieldWriterPreferences() { // This constructor is only to allow an empty constructor in SavePreferences this(true, Collections.emptyList(), new FieldContentFormatterPreferences()); diff --git a/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java b/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java new file mode 100644 index 00000000000..312df6bbf98 --- /dev/null +++ b/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java @@ -0,0 +1,83 @@ +package org.jabref.logic.bst; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.jabref.logic.cleanup.ConvertToBibtexCleanup; +import org.jabref.logic.formatter.bibtexfields.RemoveNewlinesFormatter; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.layout.format.LatexToUnicodeFormatter; +import org.jabref.logic.layout.format.RemoveLatexCommandsFormatter; +import org.jabref.logic.layout.format.RemoveTilde; +import org.jabref.logic.preview.PreviewLayout; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BstPreviewLayout implements PreviewLayout { + + private static final Logger LOGGER = LoggerFactory.getLogger(BstPreviewLayout.class); + + private final String name; + + private VM vm; + private String error; + + public BstPreviewLayout(Path path) { + name = path.getFileName().toString(); + if (!Files.exists(path)) { + LOGGER.error("File {} not found", path.toAbsolutePath()); + error = Localization.lang("Error opening file '%0'.", path.toString()); + return; + } + try { + vm = new VM(path.toFile()); + } catch (Exception e) { + LOGGER.error("Could not read {}.", path.toAbsolutePath(), e); + error = Localization.lang("Error opening file '%0'.", path.toString()); + } + } + + @Override + public String generatePreview(BibEntry originalEntry, BibDatabase database) { + if (error != null) { + return error; + } + // ensure that the entry is of BibTeX format (and do not modify the original entry) + BibEntry entry = (BibEntry) originalEntry.clone(); + new ConvertToBibtexCleanup().cleanup(entry); + String result = vm.run(List.of(entry)); + // Remove all comments + result = result.replaceAll("%.*", ""); + // Remove all LaTeX comments + // The RemoveLatexCommandsFormatter keeps the words inside latex environments. Therefore, we remove them manually + result = result.replace("\\begin{thebibliography}{1}", ""); + result = result.replace("\\end{thebibliography}", ""); + // The RemoveLatexCommandsFormatter keeps the word inside the latex command, but we want to remove that completely + result = result.replaceAll("\\\\bibitem[{].*[}]", ""); + // We want to replace \newblock by a space instead of completely removing it + result = result.replace("\\newblock", " "); + // remove all latex commands statements - assumption: command in a separate line + result = result.replaceAll("(?m)^\\\\.*$", ""); + // remove some IEEEtran.bst output (resulting from a multiline \providecommand) + result = result.replace("#2}}", ""); + // Have quotes right - and more + result = new LatexToUnicodeFormatter().format(result); + result = result.replace("``", "\""); + result = result.replace("''", "\""); + // Final cleanup + result = new RemoveNewlinesFormatter().format(result); + result = new RemoveLatexCommandsFormatter().format(result); + result = new RemoveTilde().format(result); + result = result.trim().replaceAll(" +", " "); + return result; + } + + @Override + public String getName() { + return name; + } +} diff --git a/src/main/java/org/jabref/logic/bst/VM.java b/src/main/java/org/jabref/logic/bst/VM.java index 448266e9c89..f3dce6d5cfb 100644 --- a/src/main/java/org/jabref/logic/bst/VM.java +++ b/src/main/java/org/jabref/logic/bst/VM.java @@ -14,9 +14,13 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.jabref.logic.bibtex.FieldWriter; +import org.jabref.logic.bibtex.FieldWriterPreferences; +import org.jabref.logic.bibtex.InvalidFieldValueException; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.AuthorList; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.Month; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.StandardField; @@ -136,7 +140,7 @@ private VM(CommonTree tree) { stack.push(((Integer) o1).compareTo((Integer) o2) > 0 ? VM.TRUE : VM.FALSE); }); - /* Analogous. */ + /* Analogous to >. */ buildInFunctions.put("<", context -> { if (stack.size() < 2) { throw new VMException("Not enough operands on stack for operation <"); @@ -270,7 +274,7 @@ private VM(CommonTree tree) { if (context == null) { throw new VMException("Call.type$ can only be called from within a context (ITERATE or REVERSE)."); } - VM.this.execute(context.getBibtexEntry().getType().getName(), context); + VM.this.execute(context.entry.getType().getName(), context); }); buildInFunctions.put("change.case$", new ChangeCaseFunction(this)); @@ -303,7 +307,7 @@ private VM(CommonTree tree) { if (context == null) { throw new VMException("Must have an entry to cite$"); } - stack.push(context.getBibtexEntry().getCiteKeyOptional().orElse(null)); + stack.push(context.entry.getCiteKeyOptional().orElse(null)); }); /* @@ -581,7 +585,7 @@ private VM(CommonTree tree) { throw new VMException("type$ need a context."); } - stack.push(context.getBibtexEntry().getType().getName()); + stack.push(context.entry.getType().getName()); }); /* @@ -843,11 +847,14 @@ public String run(Collection bibtex) { } /** - * @param bibtex list of entries to convert + * Transforms the given list of BibEntries to a rendered list of references using the underlying bst file + * + * @param bibEntries list of entries to convert * @param bibDatabase (may be null) the bibDatabase used for resolving strings / crossref + * @return list of references in plain text form */ - public String run(Collection bibtex, BibDatabase bibDatabase) { - Objects.requireNonNull(bibtex); + public String run(Collection bibEntries, BibDatabase bibDatabase) { + Objects.requireNonNull(bibEntries); // Reset bbl = new StringBuilder(); @@ -864,8 +871,8 @@ public String run(Collection bibtex, BibDatabase bibDatabase) { stack = new Stack<>(); // Create entries - entries = new ArrayList<>(bibtex.size()); - for (BibEntry entry : bibtex) { + entries = new ArrayList<>(bibEntries.size()); + for (BibEntry entry : bibEntries) { entries.add(new BstEntry(entry)); } @@ -921,17 +928,40 @@ public String run(Collection bibtex, BibDatabase bibDatabase) { * @param bibDatabase */ private void read(BibDatabase bibDatabase) { + FieldWriter fieldWriter = new FieldWriter(new FieldWriterPreferences()); for (BstEntry e : entries) { - for (Map.Entry mEntry : e.getFields().entrySet()) { + for (Map.Entry mEntry : e.fields.entrySet()) { Field field = FieldFactory.parseField(mEntry.getKey()); - String fieldValue = e.getBibtexEntry().getResolvedFieldOrAlias(field, bibDatabase).orElse(null); + String fieldValue = e.entry.getResolvedFieldOrAlias(field, bibDatabase) + .map(content -> { + try { + String result = fieldWriter.write(field, content); + if (result.startsWith("{")) { + // Strip enclosing {} from the output + return result.substring(1, result.length() - 1); + } + if (field == StandardField.MONTH) { + // We don't have the internal BibTeX strings at hand. + // We nevertheless want to have the full month name. + // Thus, we lookup the full month name here. + return Month.parse(result) + .map(month -> month.getFullName()) + .orElse(result); + } + return result; + } catch (InvalidFieldValueException invalidFieldValueException) { + // in case there is something wrong with the content, just return the content itself + return content; + } + }) + .orElse(null); mEntry.setValue(fieldValue); } } for (BstEntry e : entries) { - if (!e.getFields().containsKey(StandardField.CROSSREF.getName())) { - e.getFields().put(StandardField.CROSSREF.getName(), null); + if (!e.fields.containsKey(StandardField.CROSSREF.getName())) { + e.fields.put(StandardField.CROSSREF.getName(), null); } } } @@ -978,7 +1008,7 @@ private void entry(Tree child) { String name = t.getChild(i).getText(); for (BstEntry entry : entries) { - entry.getFields().put(name, null); + entry.fields.put(name, null); } } @@ -1103,8 +1133,8 @@ private void execute(String name, BstEntry context) { if (context != null) { - if (context.getFields().containsKey(name)) { - stack.push(context.getFields().get(name)); + if (context.fields.containsKey(name)) { + stack.push(context.fields.get(name)); return; } if (context.localStrings.containsKey(name)) { @@ -1171,25 +1201,18 @@ private void strings(Tree child) { public static class BstEntry { - private final BibEntry entry; + public final BibEntry entry; - private final Map localStrings = new HashMap<>(); + public final Map localStrings = new HashMap<>(); - private final Map fields = new HashMap<>(); + // keys filled by org.jabref.logic.bst.VM.entry based on the contents of the bst file + public final Map fields = new HashMap<>(); - private final Map localIntegers = new HashMap<>(); + public final Map localIntegers = new HashMap<>(); public BstEntry(BibEntry e) { this.entry = e; } - - public Map getFields() { - return fields; - } - - public BibEntry getBibtexEntry() { - return entry; - } } private void push(Integer integer) { diff --git a/src/main/java/org/jabref/logic/citationstyle/CitationStyleCache.java b/src/main/java/org/jabref/logic/citationstyle/CitationStyleCache.java index 0e9a546b2c3..bb7ba7f1f50 100644 --- a/src/main/java/org/jabref/logic/citationstyle/CitationStyleCache.java +++ b/src/main/java/org/jabref/logic/citationstyle/CitationStyleCache.java @@ -2,6 +2,7 @@ import java.util.Objects; +import org.jabref.logic.preview.PreviewLayout; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.event.EntriesRemovedEvent; import org.jabref.model.entry.BibEntry; diff --git a/src/main/java/org/jabref/logic/citationstyle/CitationStylePreviewLayout.java b/src/main/java/org/jabref/logic/citationstyle/CitationStylePreviewLayout.java index c07bce6ebfd..d40e86c7414 100644 --- a/src/main/java/org/jabref/logic/citationstyle/CitationStylePreviewLayout.java +++ b/src/main/java/org/jabref/logic/citationstyle/CitationStylePreviewLayout.java @@ -1,5 +1,6 @@ package org.jabref.logic.citationstyle; +import org.jabref.logic.preview.PreviewLayout; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; diff --git a/src/main/java/org/jabref/logic/citationstyle/TextBasedPreviewLayout.java b/src/main/java/org/jabref/logic/layout/TextBasedPreviewLayout.java similarity index 87% rename from src/main/java/org/jabref/logic/citationstyle/TextBasedPreviewLayout.java rename to src/main/java/org/jabref/logic/layout/TextBasedPreviewLayout.java index 193a0fd6949..011eced3f4e 100644 --- a/src/main/java/org/jabref/logic/citationstyle/TextBasedPreviewLayout.java +++ b/src/main/java/org/jabref/logic/layout/TextBasedPreviewLayout.java @@ -1,18 +1,19 @@ -package org.jabref.logic.citationstyle; +package org.jabref.logic.layout; import java.io.IOException; import java.io.StringReader; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.layout.Layout; -import org.jabref.logic.layout.LayoutFormatterPreferences; -import org.jabref.logic.layout.LayoutHelper; +import org.jabref.logic.preview.PreviewLayout; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Implements the preview based JabRef's Custom export fitlters. + */ public class TextBasedPreviewLayout implements PreviewLayout { private static final Logger LOGGER = LoggerFactory.getLogger(TextBasedPreviewLayout.class); diff --git a/src/main/java/org/jabref/logic/layout/format/RemoveWhitespace.java b/src/main/java/org/jabref/logic/layout/format/RemoveWhitespace.java index 747c726a880..15dc444965e 100644 --- a/src/main/java/org/jabref/logic/layout/format/RemoveWhitespace.java +++ b/src/main/java/org/jabref/logic/layout/format/RemoveWhitespace.java @@ -4,14 +4,11 @@ /** * Remove non printable character formatter. - * - * Based on the RemoveBrackets.java class (Revision 1.2) by mortenalver */ public class RemoveWhitespace implements LayoutFormatter { @Override public String format(String fieldEntry) { - if (fieldEntry == null) { return null; } diff --git a/src/main/java/org/jabref/logic/citationstyle/PreviewLayout.java b/src/main/java/org/jabref/logic/preview/PreviewLayout.java similarity index 58% rename from src/main/java/org/jabref/logic/citationstyle/PreviewLayout.java rename to src/main/java/org/jabref/logic/preview/PreviewLayout.java index e47eec9f01c..3f6626924b0 100644 --- a/src/main/java/org/jabref/logic/citationstyle/PreviewLayout.java +++ b/src/main/java/org/jabref/logic/preview/PreviewLayout.java @@ -1,8 +1,11 @@ -package org.jabref.logic.citationstyle; +package org.jabref.logic.preview; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; +/** + * Used for displaying a rendered entry in the UI. Due to historical reasons, "rendering" is called "layout". + */ public interface PreviewLayout { String generatePreview(BibEntry entry, BibDatabase database); diff --git a/src/main/java/org/jabref/model/strings/StringUtil.java b/src/main/java/org/jabref/model/strings/StringUtil.java index a5f4a2fca0e..c3a46c0adfc 100644 --- a/src/main/java/org/jabref/model/strings/StringUtil.java +++ b/src/main/java/org/jabref/model/strings/StringUtil.java @@ -193,20 +193,19 @@ public static String getCorrectFileName(String orgName, String defaultExtension) } /** - * Formats field contents for output. Must be "symmetric" with the parse method above, - * so stored and reloaded fields are not mangled. + * Formats field contents for output. Must be "symmetric" with the parse method above, so stored and reloaded fields + * are not mangled. * - * @param in - * @param wrapAmount - * @param newline - * @return the wrapped String. + * @param in the string to wrap + * @param wrapAmount the number of characters belonging to a line of text + * @param newline the newline character(s) + * @return the wrapped string */ public static String wrap(String in, int wrapAmount, String newline) { - String[] lines = in.split("\n"); StringBuilder result = new StringBuilder(); // remove all whitespace at the end of the string, this especially includes \r created when the field content has \r\n as line separator - addWrappedLine(result, CharMatcher.whitespace().trimTrailingFrom(lines[0]), wrapAmount, newline); // See + addWrappedLine(result, CharMatcher.whitespace().trimTrailingFrom(lines[0]), wrapAmount, newline); for (int i = 1; i < lines.length; i++) { if (lines[i].trim().isEmpty()) { @@ -225,12 +224,21 @@ public static String wrap(String in, int wrapAmount, String newline) { return result.toString(); } - private static void addWrappedLine(StringBuilder result, String line, int wrapAmount, String newline) { + /** + * Appends a text to a string builder. Wraps the text so that each line is approx wrapAmount characters long. + * Wrapping is done using newline and tab character. + * + * @param line the line of text to be wrapped and appended + * @param wrapAmount the number of characters belonging to a line of text + * @param newlineString a string containing the newline character(s) + */ + private static void addWrappedLine(StringBuilder result, String line, int wrapAmount, String newlineString) { // Set our pointer to the beginning of the new line in the StringBuffer: int length = result.length(); // Add the line, unmodified: result.append(line); + // insert newlines and one tab character at each position, where wrapping is necessary while (length < result.length()) { int current = result.indexOf(" ", length + wrapAmount); if ((current < 0) || (current >= result.length())) { @@ -238,9 +246,8 @@ private static void addWrappedLine(StringBuilder result, String line, int wrapAm } result.deleteCharAt(current); - result.insert(current, newline + "\t"); - length = current + newline.length(); - + result.insert(current, newlineString + "\t"); + length = current + newlineString.length(); } } diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 096396915f3..82f8ca3dd1e 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -59,8 +59,6 @@ import org.jabref.logic.bibtexkeypattern.BibtexKeyPatternPreferences; import org.jabref.logic.citationstyle.CitationStyle; import org.jabref.logic.citationstyle.CitationStylePreviewLayout; -import org.jabref.logic.citationstyle.PreviewLayout; -import org.jabref.logic.citationstyle.TextBasedPreviewLayout; import org.jabref.logic.cleanup.CleanupPreferences; import org.jabref.logic.cleanup.CleanupPreset; import org.jabref.logic.cleanup.Cleanups; @@ -74,6 +72,7 @@ import org.jabref.logic.l10n.Language; import org.jabref.logic.l10n.Localization; import org.jabref.logic.layout.LayoutFormatterPreferences; +import org.jabref.logic.layout.TextBasedPreviewLayout; import org.jabref.logic.layout.format.FileLinkPreferences; import org.jabref.logic.layout.format.NameFormatterPreferences; import org.jabref.logic.net.ProxyPreferences; @@ -81,6 +80,7 @@ import org.jabref.logic.openoffice.StyleLoader; import org.jabref.logic.preferences.OwnerPreferences; import org.jabref.logic.preferences.TimestampPreferences; +import org.jabref.logic.preview.PreviewLayout; import org.jabref.logic.protectedterms.ProtectedTermsList; import org.jabref.logic.protectedterms.ProtectedTermsLoader; import org.jabref.logic.protectedterms.ProtectedTermsPreferences; diff --git a/src/main/java/org/jabref/preferences/PreviewPreferences.java b/src/main/java/org/jabref/preferences/PreviewPreferences.java index efec4035934..c6a7ad20742 100644 --- a/src/main/java/org/jabref/preferences/PreviewPreferences.java +++ b/src/main/java/org/jabref/preferences/PreviewPreferences.java @@ -3,9 +3,9 @@ import java.util.List; import org.jabref.Globals; -import org.jabref.logic.citationstyle.PreviewLayout; -import org.jabref.logic.citationstyle.TextBasedPreviewLayout; import org.jabref.logic.layout.LayoutFormatterPreferences; +import org.jabref.logic.layout.TextBasedPreviewLayout; +import org.jabref.logic.preview.PreviewLayout; public class PreviewPreferences { diff --git a/src/test/java/org/jabref/logic/bst/BstPreviewLayoutTest.java b/src/test/java/org/jabref/logic/bst/BstPreviewLayoutTest.java new file mode 100644 index 00000000000..945d5294abe --- /dev/null +++ b/src/test/java/org/jabref/logic/bst/BstPreviewLayoutTest.java @@ -0,0 +1,66 @@ +package org.jabref.logic.bst; + +import java.nio.file.Paths; + +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +class BstPreviewLayoutTest { + + private BibDatabase bibDatabase = mock(BibDatabase.class); + + @Test + public void generatePreviewForSimpleEntryUsingAbbr() throws Exception { + BstPreviewLayout bstPreviewLayout = new BstPreviewLayout(Paths.get(BstPreviewLayoutTest.class.getResource("abbrv.bst").toURI())); + BibEntry entry = new BibEntry().withField(StandardField.AUTHOR, "Oliver Kopp") + .withField(StandardField.TITLE, "Thoughts on Development"); + BibDatabase bibDatabase = mock(BibDatabase.class); + String preview = bstPreviewLayout.generatePreview(entry, bibDatabase); + assertEquals("O. Kopp. Thoughts on development.", preview); + } + + @Test + public void monthMayIsCorrectlyRendered() throws Exception { + BstPreviewLayout bstPreviewLayout = new BstPreviewLayout(Paths.get(BstPreviewLayoutTest.class.getResource("abbrv.bst").toURI())); + BibEntry entry = new BibEntry().withField(StandardField.AUTHOR, "Oliver Kopp") + .withField(StandardField.TITLE, "Thoughts on Development") + .withField(StandardField.MONTH, "#May#"); + BibDatabase bibDatabase = mock(BibDatabase.class); + String preview = bstPreviewLayout.generatePreview(entry, bibDatabase); + assertEquals("O. Kopp. Thoughts on development, May.", preview); + } + + @Test + public void generatePreviewForSliceTheoremPaperUsingAbbr() throws Exception { + BstPreviewLayout bstPreviewLayout = new BstPreviewLayout(Paths.get(BstPreviewLayoutTest.class.getResource("abbrv.bst").toURI())); + String preview = bstPreviewLayout.generatePreview(getSliceTheoremPaper(), bibDatabase); + assertEquals("T. Diez. Slice theorem for fréchet group actions and covariant symplectic field theory. May 2014.", preview); + } + + @Test + public void generatePreviewForSliceTheoremPaperUsingIEEE() throws Exception { + BstPreviewLayout bstPreviewLayout = new BstPreviewLayout(Paths.get(ClassLoader.getSystemResource("bst/IEEEtran.bst").toURI())); + String preview = bstPreviewLayout.generatePreview(getSliceTheoremPaper(), bibDatabase); + assertEquals("T. Diez, \"Slice theorem for fréchet group actions and covariant symplectic field theory\" May 2014.", preview); + } + + private static BibEntry getSliceTheoremPaper() { + return new BibEntry(StandardEntryType.Article) + .withField(StandardField.AUTHOR, "Tobias Diez") + .withField(StandardField.TITLE, "Slice theorem for Fréchet group actions and covariant symplectic field theory") + .withField(StandardField.DATE, "2014-05-09") + .withField(StandardField.ABSTRACT, "A general slice theorem for the action of a Fr\\'echet Lie group on a Fr\\'echet manifolds is established. The Nash-Moser theorem provides the fundamental tool to generalize the result of Palais to this infinite-dimensional setting. The presented slice theorem is illustrated by its application to gauge theories: the action of the gauge transformation group admits smooth slices at every point and thus the gauge orbit space is stratified by Fr\\'echet manifolds. Furthermore, a covariant and symplectic formulation of classical field theory is proposed and extensively discussed. At the root of this novel framework is the incorporation of field degrees of freedom F and spacetime M into the product manifold F * M. The induced bigrading of differential forms is used in order to carry over the usual symplectic theory to this new setting. The examples of the Klein-Gordon field and general Yang-Mills theory illustrate that the presented approach conveniently handles the occurring symmetries.") + .withField(StandardField.EPRINT, "1405.2249v1") + .withField(StandardField.FILE, ":http\\://arxiv.org/pdf/1405.2249v1:PDF") + .withField(StandardField.EPRINTTYPE, "arXiv") + .withField(StandardField.EPRINTCLASS, "math-ph") + .withField(StandardField.KEYWORDS, "math-ph, math.DG, math.MP, math.SG, 58B99, 58Z05, 58B25, 22E65, 58D19, 53D20, 53D42"); + } +} diff --git a/src/test/java/org/jabref/logic/bst/TestVM.java b/src/test/java/org/jabref/logic/bst/TestVM.java index 35d42d7e217..f468889d55a 100644 --- a/src/test/java/org/jabref/logic/bst/TestVM.java +++ b/src/test/java/org/jabref/logic/bst/TestVM.java @@ -54,7 +54,7 @@ public void testVMSimple() throws RecognitionException, IOException { assertEquals(2, vm.getStrings().size()); assertEquals(7, vm.getIntegers().size()); assertEquals(1, vm.getEntries().size()); - assertEquals(5, vm.getEntries().get(0).getFields().size()); + assertEquals(5, vm.getEntries().get(0).fields.size()); assertEquals(38, vm.getFunctions().size()); } @@ -366,10 +366,10 @@ public void testSort() throws RecognitionException, IOException { vm.run(v); List v2 = vm.getEntries(); - assertEquals(Optional.of("a"), v2.get(0).getBibtexEntry().getCiteKeyOptional()); - assertEquals(Optional.of("b"), v2.get(1).getBibtexEntry().getCiteKeyOptional()); - assertEquals(Optional.of("c"), v2.get(2).getBibtexEntry().getCiteKeyOptional()); - assertEquals(Optional.of("d"), v2.get(3).getBibtexEntry().getCiteKeyOptional()); + assertEquals(Optional.of("a"), v2.get(0).entry.getCiteKeyOptional()); + assertEquals(Optional.of("b"), v2.get(1).entry.getCiteKeyOptional()); + assertEquals(Optional.of("c"), v2.get(2).entry.getCiteKeyOptional()); + assertEquals(Optional.of("d"), v2.get(3).entry.getCiteKeyOptional()); } @Test