From f4926ca51f4d1cd71ba334758592215170db02f4 Mon Sep 17 00:00:00 2001 From: sreenath-tm Date: Sat, 10 Sep 2022 19:14:50 +0530 Subject: [PATCH] Anomaly with Title case and Sentence case #9112 (#9142) --- CHANGELOG.md | 2 +- .../casechanger/TitleCaseFormatter.java | 8 ++-- .../logic/formatter/casechanger/Word.java | 45 +++++++++++++++++-- .../SentenceCaseFormatterTest.java | 2 + .../casechanger/TitleCaseFormatterTest.java | 2 + 5 files changed, 51 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 124ca3409b9..fb6b2aa76dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We fixed several bugs regarding the manual and the autosave of library files that sometimes lead to exceptions or data loss. [#9067](https://github.com/JabRef/jabref/pull/9067), [#8448](https://github.com/JabRef/jabref/issues/8484), [#8746](https://github.com/JabRef/jabref/issues/8746), [#6684](https://github.com/JabRef/jabref/issues/6684), [#6644](https://github.com/JabRef/jabref/issues/6644), [#6102](https://github.com/JabRef/jabref/issues/6102), [#6002](https://github.com/JabRef/jabref/issues/6000) - We fixed an issue where applied save actions on saving the library file would lead to the dialog "The library has been modified by another program" popping up [#4877](https://github.com/JabRef/jabref/issues/4877) - We fixed issues with save actions not correctly loaded when opening the library. [#9122](https://github.com/JabRef/jabref/pull/9122) -- We fixed an issue where title case didn't capitalize words after en-dash characters. [#9068](https://github.com/JabRef/jabref/pull/9068) +- We fixed an issue where title case didn't capitalize words after en-dash characters and skip capitalization of conjunctions that comes after en-dash characters. [#9068](https://github.com/JabRef/jabref/pull/9068),[#9142](https://github.com/JabRef/jabref/pull/9142) - We fixed an issue where JabRef would not exit when a connection to a LibreOffice document was established previously and the document is still open. [#9075](https://github.com/JabRef/jabref/issues/9075) - We fixed an issue about selecting the save order in the preferences. [#9175](https://github.com/JabRef/jabref/issues/9147) diff --git a/src/main/java/org/jabref/logic/formatter/casechanger/TitleCaseFormatter.java b/src/main/java/org/jabref/logic/formatter/casechanger/TitleCaseFormatter.java index 19012649afe..47b1100c056 100644 --- a/src/main/java/org/jabref/logic/formatter/casechanger/TitleCaseFormatter.java +++ b/src/main/java/org/jabref/logic/formatter/casechanger/TitleCaseFormatter.java @@ -31,14 +31,14 @@ public String format(String input) { Title title = new Title(sentence); title.getWords().stream().filter(Word::isSmallerWord).forEach(Word::toLowerCase); - title.getWords().stream().filter(Word::isLargerWord).forEach(Word::toUpperFirst); + title.getWords().stream().filter(Word::isLargerWord).forEach(Word::toUpperFirstTitle); - title.getFirstWord().ifPresent(Word::toUpperFirst); - title.getLastWord().ifPresent(Word::toUpperFirst); + title.getFirstWord().ifPresent(Word::toUpperFirstTitle); + title.getLastWord().ifPresent(Word::toUpperFirstTitle); for (int i = 0; i < (title.getWords().size() - 2); i++) { if (title.getWords().get(i).endsWithColon()) { - title.getWords().get(i + 1).toUpperFirst(); + title.getWords().get(i + 1).toUpperFirstTitle(); } } diff --git a/src/main/java/org/jabref/logic/formatter/casechanger/Word.java b/src/main/java/org/jabref/logic/formatter/casechanger/Word.java index dbc162fc8a5..9cf9dd77ea9 100644 --- a/src/main/java/org/jabref/logic/formatter/casechanger/Word.java +++ b/src/main/java/org/jabref/logic/formatter/casechanger/Word.java @@ -18,20 +18,23 @@ public final class Word { */ public static final Set SMALLER_WORDS; public static final Set DASHES; + public static final Set CONJUNCTIONS; private final char[] chars; private final boolean[] protectedChars; static { Set smallerWords = new HashSet<>(); Set dashes = new HashSet<>(); + Set conjunctions = new HashSet<>(); + // Conjunctions used as part of Title case capitalisation to specifically check if word is conjunction or not + conjunctions.addAll(Arrays.asList("and", "but", "for", "nor", "or", "so", "yet")); // Articles smallerWords.addAll(Arrays.asList("a", "an", "the")); // Prepositions smallerWords.addAll(Arrays.asList("above", "about", "across", "against", "along", "among", "around", "at", "before", "behind", "below", "beneath", "beside", "between", "beyond", "by", "down", "during", "except", "for", "from", "in", "inside", "into", "like", "near", "of", "off", "on", "onto", "since", "to", "toward", "through", "under", "until", "up", "upon", "with", "within", "without")); - // Conjunctions - smallerWords.addAll(Arrays.asList("and", "but", "for", "nor", "or", "so", "yet")); - + // Conjunctions used as part of all case capitalisation to check if it is a small word or not + smallerWords.addAll(conjunctions); // Dashes dashes.addAll(Arrays.asList( '-', '~', '⸗', '〰', '᐀', '֊', '־', '‐', '‑', '‒', @@ -42,6 +45,9 @@ public final class Word { // unmodifiable for thread safety DASHES = dashes; + // unmodifiable for thread safety + CONJUNCTIONS = conjunctions; + // unmodifiable for thread safety SMALLER_WORDS = smallerWords.stream() .map(word -> word.toLowerCase(Locale.ROOT)) @@ -87,6 +93,39 @@ public void toLowerCase() { } public void toUpperFirst() { + for (int i = 0; i < chars.length; i++) { + if (!protectedChars[i]) { + chars[i] = (i == 0) ? + Character.toUpperCase(chars[i]) : + Character.toLowerCase(chars[i]); + } + } + } + + public void toUpperFirstTitle() { + for (int i = 0; i < chars.length; i++) { + if (!protectedChars[i]) { + chars[i] = (i == 0 || (DASHES.contains(chars[i - 1]) && isConjunction(chars, i))) ? + Character.toUpperCase(chars[i]) : + Character.toLowerCase(chars[i]); + } + } + } + + private boolean isConjunction(char[] chars, int i) { + String word = ""; + while (i < chars.length && !DASHES.contains(chars[i])) { + word += chars[i]; + i++; + } + if (CONJUNCTIONS.contains(word)) { + return false; + } else { + return true; + } + } + + public void stripConsonants() { for (int i = 0; i < chars.length; i++) { if (!protectedChars[i]) { chars[i] = (i == 0 || DASHES.contains(chars[i - 1])) ? diff --git a/src/test/java/org/jabref/logic/formatter/casechanger/SentenceCaseFormatterTest.java b/src/test/java/org/jabref/logic/formatter/casechanger/SentenceCaseFormatterTest.java index ddbc7ab6a2e..38bea3eca23 100644 --- a/src/test/java/org/jabref/logic/formatter/casechanger/SentenceCaseFormatterTest.java +++ b/src/test/java/org/jabref/logic/formatter/casechanger/SentenceCaseFormatterTest.java @@ -35,6 +35,8 @@ private static Stream testData() { "England’s Monitor; the History of the Separation"), Arguments.of("Dr. schultz: a dentist turned bounty hunter.", "Dr. schultz: a dentist turned bounty hunter."), + Arguments.of("Wetting-and-drying", + "wetting-and-drying"), Arguments.of("Example case. {EXCLUDED SENTENCE.}", "Example case. {EXCLUDED SENTENCE.}"), Arguments.of("I have {Aa} dream", new SentenceCaseFormatter().getExampleInput())); diff --git a/src/test/java/org/jabref/logic/formatter/casechanger/TitleCaseFormatterTest.java b/src/test/java/org/jabref/logic/formatter/casechanger/TitleCaseFormatterTest.java index 20ebb96648f..2538af947a2 100644 --- a/src/test/java/org/jabref/logic/formatter/casechanger/TitleCaseFormatterTest.java +++ b/src/test/java/org/jabref/logic/formatter/casechanger/TitleCaseFormatterTest.java @@ -55,6 +55,8 @@ private static Stream testData() { "⁻⁻test−ing dash⸻like characters"), Arguments.of("--Test〰Ing M-U~L゠T︱I⁓P︲L--~~︲E Dash⸻-Like Characters", "--test〰ing M-u~l゠t︱i⁓p︲l--~~︲e dASH⸻-likE charACTErs"), + Arguments.of("--Wetting-and-Drying M-U~L゠T︱I⁓P︲L--~~︲E Dash⸻-Like Characters", + "--wetting-and-drying M-u~l゠t︱i⁓p︲l--~~︲e dASH⸻-likE charACTErs"), Arguments.of("Kinetic Studies on Enzyme-Catalyzed Reactions: Oxidation of Glucose, Decomposition of Hydrogen Peroxide and Their Combination", "kinetic studies on enzyme-catalyzed reactions: oxidation of glucose, decomposition of hydrogen peroxide and their combination"), Arguments.of("{BPMN} Conformance in Open Source Engines",