diff --git a/src/main/java/net/sf/jabref/exporter/SaveDatabaseAction.java b/src/main/java/net/sf/jabref/exporter/SaveDatabaseAction.java index c7be09a76ea..286be20cd2b 100644 --- a/src/main/java/net/sf/jabref/exporter/SaveDatabaseAction.java +++ b/src/main/java/net/sf/jabref/exporter/SaveDatabaseAction.java @@ -25,9 +25,11 @@ import net.sf.jabref.collab.ChangeScanner; import net.sf.jabref.gui.worker.CallBack; import net.sf.jabref.gui.worker.Worker; +import net.sf.jabref.logic.cleanup.AutoFormatter; import net.sf.jabref.logic.l10n.Encodings; import net.sf.jabref.logic.l10n.Localization; import net.sf.jabref.logic.util.io.FileBasedLock; +import net.sf.jabref.model.entry.BibtexEntry; import javax.swing.*; import java.io.File; @@ -52,7 +54,6 @@ public class SaveDatabaseAction extends AbstractWorker { public SaveDatabaseAction(BasePanel panel) { - this.panel = panel; this.frame = panel.frame(); } @@ -184,6 +185,12 @@ public void run() { // lacking keys, before saving: panel.autoGenerateKeysBeforeSaving(); + // onSave cleanup actions + for(BibtexEntry entry: panel.database.getEntries()) { + AutoFormatter formatter = new AutoFormatter(entry); + formatter.runDefaultCleanups(); + } + if (!FileBasedLock.waitForFileLock(panel.getFile(), 10)) { success = false; fileLockedError = true; diff --git a/src/main/java/net/sf/jabref/logic/cleanup/AutoFormatter.java b/src/main/java/net/sf/jabref/logic/cleanup/AutoFormatter.java new file mode 100644 index 00000000000..f72f835e167 --- /dev/null +++ b/src/main/java/net/sf/jabref/logic/cleanup/AutoFormatter.java @@ -0,0 +1,58 @@ +package net.sf.jabref.logic.cleanup; + +import net.sf.jabref.model.entry.BibtexEntry; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This class includes sensible defaults for consistent formatting of BibTex entries. + */ +public class AutoFormatter { + private BibtexEntry entry; + + public AutoFormatter(BibtexEntry entry) { + this.entry = entry; + } + + /** + * Runs all default cleanups for the BibTex entry. + */ + public void runDefaultCleanups() { + applySuperscripts(); + } + + /** + * Converts ordinal numbers to superscripts, e.g. 1st, 2nd or 3rd. + * Run the replacement for every available BibTex field. + * Will replace ordinal numbers even if they are semantically wrong, e.g. 21rd + * + * + * 1st Conf. Cloud Computing -> 1\textsuperscript{st} Conf. Cloud Computing + * + */ + public void applySuperscripts() { + // find possible superscripts on word boundaries + final Pattern pattern = Pattern.compile("\\b(\\d+)(st|nd|rd|th)\\b", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + // adds superscript tag + final String replace = "$1\\\\textsuperscript{$2}"; + + for(String name: entry.getAllFields()) { + String value = entry.getField(name); + + // nothing to do + if (value == null || value.isEmpty()) { + continue; + } + + Matcher matcher = pattern.matcher(value); + // replace globally + String newValue = matcher.replaceAll(replace); + + // write field + if(!newValue.equals(value)) { + entry.setField(name, newValue); + } + } + } +} diff --git a/src/test/java/net/sf/jabref/logic/cleanup/AutoFormatterTest.java b/src/test/java/net/sf/jabref/logic/cleanup/AutoFormatterTest.java new file mode 100644 index 00000000000..b900f9e4748 --- /dev/null +++ b/src/test/java/net/sf/jabref/logic/cleanup/AutoFormatterTest.java @@ -0,0 +1,112 @@ +package net.sf.jabref.logic.cleanup; + +import junit.framework.Assert; +import net.sf.jabref.model.entry.BibtexEntry; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class AutoFormatterTest { + private BibtexEntry entry; + + @Before + public void setUp() { + entry = new BibtexEntry(); + } + + @After + public void teardown() { + entry = null; + } + + @Test + public void replacesSuperscript() { + entry.setField("field one", "1st"); + entry.setField("field two", "2nd"); + entry.setField("field three", "3rd"); + entry.setField("field four", "4th"); + entry.setField("field five", "21th"); + + new AutoFormatter(entry).applySuperscripts(); + + Assert.assertEquals("1\\textsuperscript{st}", entry.getField("field one")); + Assert.assertEquals("2\\textsuperscript{nd}", entry.getField("field two")); + Assert.assertEquals("3\\textsuperscript{rd}", entry.getField("field three")); + Assert.assertEquals("4\\textsuperscript{th}", entry.getField("field four")); + Assert.assertEquals("21\\textsuperscript{th}", entry.getField("field five")); + } + + @Test + public void replacesSuperscriptsInAllFields() { + entry.setField("field_one", "1st"); + entry.setField("field_two", "1st"); + + new AutoFormatter(entry).applySuperscripts(); + + for(String name: entry.getAllFields()) { + Assert.assertEquals("1\\textsuperscript{st}", entry.getField(name)); + } + } + + @Test + public void replaceSuperscriptsEmptyFields() { + entry.setField("empty field", ""); + entry.setField("null field", null); + + new AutoFormatter(entry).applySuperscripts(); + + Assert.assertEquals("", entry.getField("empty field")); + Assert.assertEquals(null, entry.getField("null field")); + } + + @Test + public void replaceSuperscriptsIgnoresCase() { + entry.setField("lowercase", "1st"); + entry.setField("uppercase", "1ST"); + entry.setField("mixedcase", "1sT"); + + new AutoFormatter(entry).applySuperscripts(); + + Assert.assertEquals("1\\textsuperscript{st}", entry.getField("lowercase")); + Assert.assertEquals("1\\textsuperscript{ST}", entry.getField("uppercase")); + Assert.assertEquals("1\\textsuperscript{sT}", entry.getField("mixedcase")); + } + + @Test + public void replaceSuperscriptsInMultilineStrings() { + entry.setField("multiline", "replace on 1st line\nand on 2nd line."); + + new AutoFormatter(entry).applySuperscripts(); + + Assert.assertEquals( + "replace on 1\\textsuperscript{st} line\nand on 2\\textsuperscript{nd} line.", + entry.getField("multiline") + ); + } + + @Test + public void replaceAllSuperscripts() { + entry.setField("multiple", "1st 2nd 3rd 4th"); + + new AutoFormatter(entry).applySuperscripts(); + + Assert.assertEquals( + "1\\textsuperscript{st} 2\\textsuperscript{nd} 3\\textsuperscript{rd} 4\\textsuperscript{th}", + entry.getField("multiple") + ); + } + + @Test + public void ignoreSuperscriptsInsideWords() { + entry.setField("word boundaries", "1st 1stword words1st inside1stwords"); + + new AutoFormatter(entry).applySuperscripts(); + + Assert.assertEquals( + "1\\textsuperscript{st} 1stword words1st inside1stwords", + entry.getField("word boundaries") + ); + } +} \ No newline at end of file