Skip to content

Commit f6ea6a9

Browse files
authored
CSL4LibreOffice - D [GSoC '24] (JabRef#11636)
* Refactor CitationStyleGeneratorTest * Add test: [StAX] Parse title, isNumericStyle * Add CSLFormatUtils * Refactor CSLCitationOOAdapter and add JavaDoc * Add test for citeproc DIN 1505-2 * Better method names, more javadoc * Add tests for CSLFormatUtils * Add javadoc for adapter * Fix locales * Fix submodules for styles * OpenRewrite * Fix submodules for styles * Fix locales * Rename test method * Change order of methods in mark manager * Fix submodules for styles * Fix locales * Disable test * Review actions - I * Better javadoc for disabled test * Remove "public" as per best practices * Review changes [3] * Swap arguments for CitationStyleTest * Add comment to @disabled
1 parent ba4bd2d commit f6ea6a9

File tree

7 files changed

+1068
-200
lines changed

7 files changed

+1068
-200
lines changed

src/main/java/org/jabref/logic/citationstyle/CitationStyle.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.jabref.logic.openoffice.style.OOStyle;
2727
import org.jabref.logic.util.StandardFileType;
2828

29+
import com.google.common.annotations.VisibleForTesting;
2930
import org.slf4j.Logger;
3031
import org.slf4j.LoggerFactory;
3132

@@ -76,7 +77,8 @@ private static Optional<CitationStyle> createCitationStyleFromSource(final Input
7677
public record StyleInfo(String title, boolean isNumericStyle) {
7778
}
7879

79-
private static Optional<StyleInfo> parseStyleInfo(String filename, String content) {
80+
@VisibleForTesting
81+
static Optional<StyleInfo> parseStyleInfo(String filename, String content) {
8082
FACTORY.setProperty(XMLInputFactory.IS_COALESCING, true);
8183

8284
try {

src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java

Lines changed: 80 additions & 160 deletions
Large diffs are not rendered by default.
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package org.jabref.logic.openoffice.oocsltext;
2+
3+
import java.util.List;
4+
import java.util.Optional;
5+
import java.util.regex.Matcher;
6+
import java.util.regex.Pattern;
7+
8+
import org.jabref.logic.citationkeypattern.BracketedPattern;
9+
import org.jabref.logic.citationstyle.CitationStyleOutputFormat;
10+
import org.jabref.model.database.BibDatabaseContext;
11+
import org.jabref.model.entry.AuthorList;
12+
import org.jabref.model.entry.BibEntry;
13+
import org.jabref.model.entry.BibEntryTypesManager;
14+
import org.jabref.model.entry.field.StandardField;
15+
import org.jabref.model.openoffice.ootext.OOText;
16+
import org.jabref.model.openoffice.ootext.OOTextIntoOO;
17+
18+
import com.sun.star.text.XTextCursor;
19+
import com.sun.star.text.XTextDocument;
20+
import org.apache.commons.text.StringEscapeUtils;
21+
22+
/**
23+
* Contains utility constants and methods for processing of CSL citations as generated by methods of <a href="https://github.com/michel-kraemer/citeproc-java">citeproc-java</a> ({@link org.jabref.logic.citationstyle.CitationStyleGenerator}).
24+
* <p>These methods are used in {@link CSLCitationOOAdapter} which inserts CSL citation text into an OO document.</p>
25+
*/
26+
public class CSLFormatUtils {
27+
28+
// TODO: These are static final fields right now, should add the functionality to let user select these and store them in preferences.
29+
public static final String DEFAULT_BIBLIOGRAPHY_TITLE = "References";
30+
public static final String DEFAULT_BIBLIOGRAPHY_HEADER_PARAGRAPH_FORMAT = "Heading 2";
31+
32+
public static final CitationStyleOutputFormat OUTPUT_FORMAT = CitationStyleOutputFormat.HTML;
33+
private static final Pattern YEAR_IN_CITATION_PATTERN = Pattern.compile("(.)(.*), (\\d{4}.*)");
34+
35+
/**
36+
* Transforms provided HTML into a format that can be fully parsed and inserted into an OO document.
37+
* Context: The HTML produced by {@link org.jabref.logic.citationstyle.CitationStyleGenerator#generateCitation(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateCitation} or {@link org.jabref.logic.citationstyle.CitationStyleGenerator#generateInText(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateInText} is not directly (completely) parsable by by {@link OOTextIntoOO#write(XTextDocument, XTextCursor, OOText) write}.
38+
* For more details, read the documentation for the {@link OOTextIntoOO} class.
39+
* <a href="https://devdocs.jabref.org/code-howtos/openoffice/code-reorganization.html">Additional Information</a>.
40+
*
41+
* @param html The HTML string to be transformed into OO-write ready HTML.
42+
* @return The formatted html string.
43+
*/
44+
public static String transformHTML(String html) {
45+
// Initial clean up of escaped characters
46+
html = StringEscapeUtils.unescapeHtml4(html);
47+
48+
// Handle margins (spaces between citation number and text)
49+
html = html.replaceAll("<div class=\"csl-left-margin\">(.*?)</div><div class=\"csl-right-inline\">(.*?)</div>", "$1 $2");
50+
51+
// Remove unsupported tags
52+
html = html.replaceAll("<div[^>]*>", "");
53+
html = html.replace("</div>", "");
54+
55+
// Remove unsupported links
56+
html = html.replaceAll("<a[^>]*>", "");
57+
html = html.replace("</a>", "");
58+
59+
// Replace span tags with inline styles for bold
60+
html = html.replaceAll("<span style=\"font-weight: ?bold;?\">(.*?)</span>", "<b>$1</b>");
61+
62+
// Replace span tags with inline styles for italic
63+
html = html.replaceAll("<span style=\"font-style: ?italic;?\">(.*?)</span>", "<i>$1</i>");
64+
65+
// Replace span tags with inline styles for underline
66+
html = html.replaceAll("<span style=\"text-decoration: ?underline;?\">(.*?)</span>", "<u>$1</u>");
67+
68+
html = html.replaceAll("<span style=\"font-variant: ?small-caps;?\">(.*?)</span>", "<smallcaps>$1</smallcaps>");
69+
70+
// Clean up any remaining span tags
71+
html = html.replaceAll("</?span[^>]*>", "");
72+
73+
return html;
74+
}
75+
76+
/**
77+
* Alphanumeric citations are not natively supported by citeproc-java (see {@link org.jabref.logic.citationstyle.CitationStyleGenerator#generateInText(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateInText}).
78+
* Thus, we manually format a citation to produce its alphanumeric form.
79+
*
80+
* @param entries the list of entries for which the alphanumeric citation is to be generated.
81+
* @return the alphanumeric citation (for a single entry or a group of entries).
82+
*/
83+
public static String generateAlphanumericCitation(List<BibEntry> entries, BibDatabaseContext bibDatabaseContext) {
84+
StringBuilder citation = new StringBuilder("[");
85+
for (int i = 0; i < entries.size(); i++) {
86+
BibEntry entry = entries.get(i);
87+
Optional<String> author = entry.getResolvedFieldOrAlias(StandardField.AUTHOR, bibDatabaseContext.getDatabase());
88+
Optional<String> year = entry.getResolvedFieldOrAlias(StandardField.YEAR, bibDatabaseContext.getDatabase());
89+
90+
if (author.isPresent() && year.isPresent()) {
91+
AuthorList authorList = AuthorList.parse(author.get());
92+
String alphaKey = BracketedPattern.authorsAlpha(authorList);
93+
94+
// Extract last two digits of the year
95+
String shortYear = year.get().length() >= 2 ?
96+
year.get().substring(year.get().length() - 2) :
97+
year.get();
98+
99+
citation.append(alphaKey).append(shortYear);
100+
} else {
101+
citation.append(entry.getCitationKey().orElse(""));
102+
}
103+
104+
if (i < entries.size() - 1) {
105+
citation.append("; ");
106+
}
107+
}
108+
citation.append("]");
109+
return citation.toString();
110+
}
111+
112+
/**
113+
* Method to update citation number of a bibliographic entry (to be inserted in the list of references).
114+
* By default, citeproc-java ({@link org.jabref.logic.citationstyle.CitationStyleGenerator#generateCitation(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateCitation} always start the numbering of a list of citations with "1".
115+
* If a citation doesn't correspond to the first cited entry, the number should be changed to the relevant current citation number.
116+
* If an entries has been cited before, the colder number should be reused.
117+
* The number can be enclosed in different formats, such as "1", "1.", "1)", "(1)" or "[1]".
118+
* <p>
119+
* <b>Precondition:</b> Use ONLY with numeric citation styles.</p>
120+
*
121+
* @param citation the numeric citation with an unresolved number.
122+
* @param currentNumber the correct number to update the citation with.
123+
* @return the bibliographic citation with resolved number.
124+
*/
125+
public static String updateSingleBibliographyNumber(String citation, int currentNumber) {
126+
Pattern pattern = Pattern.compile("(\\[|\\()?(\\d+)(\\]|\\))?(\\.)?\\s*");
127+
Matcher matcher = pattern.matcher(citation);
128+
StringBuilder sb = new StringBuilder();
129+
boolean numberReplaced = false;
130+
131+
while (matcher.find()) {
132+
if (!numberReplaced) {
133+
String prefix = matcher.group(1) != null ? matcher.group(1) : "";
134+
String suffix = matcher.group(3) != null ? matcher.group(3) : "";
135+
String dot = matcher.group(4) != null ? "." : "";
136+
String space = matcher.group().endsWith(" ") ? " " : "";
137+
138+
String replacement = prefix + currentNumber + suffix + dot + space;
139+
140+
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
141+
numberReplaced = true;
142+
} else {
143+
matcher.appendReplacement(sb, matcher.group());
144+
}
145+
}
146+
matcher.appendTail(sb);
147+
return sb.toString();
148+
}
149+
150+
/**
151+
* Extracts year from a citation having single or multiple entries, for the purpose of using in in-text citations.
152+
*
153+
* @param formattedCitation the citation cleaned up and formatted using {@link CSLFormatUtils#transformHTML transformHTML}.
154+
*/
155+
public static String changeToInText(String formattedCitation) {
156+
Matcher matcher = YEAR_IN_CITATION_PATTERN.matcher(formattedCitation);
157+
if (matcher.find()) {
158+
return matcher.group(2) + " " + matcher.group(1) + matcher.group(3);
159+
}
160+
return formattedCitation;
161+
}
162+
}

src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMarkManager.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@ public CSLReferenceMarkManager(XTextDocument document) {
4343
this.citationKeyToNumber = new HashMap<>();
4444
}
4545

46+
public CSLReferenceMark createReferenceMark(BibEntry entry) throws Exception {
47+
String citationKey = entry.getCitationKey().orElse(CUID.randomCUID2(8).toString());
48+
int citationNumber = getCitationNumber(citationKey);
49+
CSLReferenceMark referenceMark = CSLReferenceMark.of(citationKey, citationNumber, factory);
50+
addMark(referenceMark);
51+
return referenceMark;
52+
}
53+
54+
public void addMark(CSLReferenceMark mark) {
55+
marksByName.put(mark.getName(), mark);
56+
idsByMark.put(mark, marksByID.size());
57+
marksByID.add(mark);
58+
updateCitationInfo(mark.getName());
59+
}
60+
4661
public void readExistingMarks() throws WrappedTargetException, NoSuchElementException {
4762
XReferenceMarksSupplier supplier = UnoRuntime.queryInterface(XReferenceMarksSupplier.class, document);
4863
XNameAccess marks = supplier.getReferenceMarks();
@@ -72,26 +87,11 @@ private void updateCitationInfo(String name) {
7287
}
7388
}
7489

75-
public void addMark(CSLReferenceMark mark) {
76-
marksByName.put(mark.getName(), mark);
77-
idsByMark.put(mark, marksByID.size());
78-
marksByID.add(mark);
79-
updateCitationInfo(mark.getName());
90+
public boolean hasCitationForKey(String citationKey) {
91+
return citationKeyToNumber.containsKey(citationKey);
8092
}
8193

8294
public int getCitationNumber(String citationKey) {
8395
return citationKeyToNumber.computeIfAbsent(citationKey, k -> ++highestCitationNumber);
8496
}
85-
86-
public CSLReferenceMark createReferenceMark(BibEntry entry) throws Exception {
87-
String citationKey = entry.getCitationKey().orElse(CUID.randomCUID2(8).toString());
88-
int citationNumber = getCitationNumber(citationKey);
89-
CSLReferenceMark referenceMark = CSLReferenceMark.of(citationKey, citationNumber, factory);
90-
addMark(referenceMark);
91-
return referenceMark;
92-
}
93-
94-
public boolean hasCitationForKey(String citationKey) {
95-
return citationKeyToNumber.containsKey(citationKey);
96-
}
9797
}

src/test/java/org/jabref/logic/citationstyle/CitationStyleGeneratorTest.java

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.jabref.logic.citationstyle;
22

3+
import java.io.IOException;
34
import java.util.List;
45
import java.util.stream.Stream;
56

@@ -13,6 +14,8 @@
1314
import org.jabref.model.entry.field.StandardField;
1415
import org.jabref.model.entry.types.StandardEntryType;
1516

17+
import de.undercouch.citeproc.output.Citation;
18+
import org.junit.jupiter.api.Disabled;
1619
import org.junit.jupiter.api.Test;
1720
import org.junit.jupiter.params.ParameterizedTest;
1821
import org.junit.jupiter.params.provider.Arguments;
@@ -22,15 +25,16 @@
2225

2326
class CitationStyleGeneratorTest {
2427

28+
private final BibEntry testEntry = TestEntry.getTestEntry();
29+
private final BibDatabaseContext context = new BibDatabaseContext(new BibDatabase(List.of(testEntry)));
2530
private final BibEntryTypesManager bibEntryTypesManager = new BibEntryTypesManager();
31+
private final List<CitationStyle> styleList = CitationStyle.discoverCitationStyles();
2632

2733
@Test
2834
void aCMCitation() {
29-
BibDatabaseContext context = new BibDatabaseContext(new BibDatabase(List.of(TestEntry.getTestEntry())));
3035
context.setMode(BibDatabaseMode.BIBLATEX);
31-
List<CitationStyle> styleList = CitationStyle.discoverCitationStyles();
32-
CitationStyle style = styleList.stream().filter(e -> "ACM SIGGRAPH".equals(e.getTitle())).findAny().orElse(null);
33-
String citation = CitationStyleGenerator.generateCitation(List.of(TestEntry.getTestEntry()), style.getSource(), CitationStyleOutputFormat.HTML, context, new BibEntryTypesManager()).getFirst();
36+
CitationStyle style = styleList.stream().filter(e -> "ACM SIGGRAPH".equals(e.getTitle())).findAny().get();
37+
String citation = CitationStyleGenerator.generateCitation(List.of(testEntry), style.getSource(), CitationStyleOutputFormat.HTML, context, bibEntryTypesManager).getFirst();
3438

3539
// if the acm-siggraph.csl citation style changes this has to be modified
3640
String expected = " <div class=\"csl-entry\">"
@@ -43,11 +47,9 @@ void aCMCitation() {
4347

4448
@Test
4549
void aPACitation() {
46-
BibDatabaseContext context = new BibDatabaseContext(new BibDatabase(List.of(TestEntry.getTestEntry())));
4750
context.setMode(BibDatabaseMode.BIBLATEX);
48-
List<CitationStyle> styleList = CitationStyle.discoverCitationStyles();
49-
CitationStyle style = styleList.stream().filter(e -> "American Psychological Association 7th edition".equals(e.getTitle())).findAny().orElse(null);
50-
String citation = CitationStyleGenerator.generateCitation(List.of(TestEntry.getTestEntry()), style.getSource(), CitationStyleOutputFormat.HTML, context, new BibEntryTypesManager()).getFirst();
51+
CitationStyle style = styleList.stream().filter(e -> "American Psychological Association 7th edition".equals(e.getTitle())).findAny().get();
52+
String citation = CitationStyleGenerator.generateCitation(List.of(testEntry), style.getSource(), CitationStyleOutputFormat.HTML, context, bibEntryTypesManager).getFirst();
5153

5254
// if the apa-7th-citation.csl citation style changes this has to be modified
5355
String expected = " <div class=\"csl-entry\">"
@@ -58,6 +60,21 @@ void aPACitation() {
5860
assertEquals(expected, citation);
5961
}
6062

63+
/**
64+
* Fails due to citeproc-java ({@link CitationStyleGenerator#generateInText(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateInText}) returning an empty citation.
65+
* Alphanumeric citations are thus, currently manually generated by formatting (see {@link org.jabref.logic.openoffice.oocsltext.CSLFormatUtils#generateAlphanumericCitation(List, BibDatabaseContext) generateAlphaNumericCitation}).
66+
*/
67+
@Test
68+
@Disabled("Till alphanumeric citations are supported by citeproc-java")
69+
void din1502AlphanumericInTextCitation() throws IOException {
70+
context.setMode(BibDatabaseMode.BIBLATEX);
71+
CitationStyle style = styleList.stream().filter(e -> "DIN 1505-2 (alphanumeric, Deutsch) - standard superseded by ISO-690".equals(e.getTitle())).findAny().get();
72+
Citation citation = CitationStyleGenerator.generateInText(List.of(testEntry), style.getSource(), CitationStyleOutputFormat.HTML, context, bibEntryTypesManager);
73+
String inTextCitationText = citation.getText();
74+
75+
assertEquals("[Smit2016]", inTextCitationText);
76+
}
77+
6178
@Test
6279
void ignoreNewLine() {
6380
BibEntry entry = new BibEntry();
@@ -97,23 +114,21 @@ void htmlFormat() {
97114
" <div class=\"csl-left-margin\">[1]</div><div class=\"csl-right-inline\">B. Smith, B. Jones, and J. Williams, &ldquo;Title of the test entry,&rdquo; <span style=\"font-style: italic\">BibTeX Journal</span>, vol. 34, no. 3, pp. 45&ndash;67, Jul. 2016, doi: 10.1001/bla.blubb.</div>\n" +
98115
" </div>\n";
99116

100-
BibEntry entry = TestEntry.getTestEntry();
101117
String style = CitationStyle.getDefault().getSource();
102118
CitationStyleOutputFormat format = CitationStyleOutputFormat.HTML;
103119

104-
String actualCitation = CitationStyleGenerator.generateCitation(List.of(entry), style, format, new BibDatabaseContext(), bibEntryTypesManager).getFirst();
120+
String actualCitation = CitationStyleGenerator.generateCitation(List.of(testEntry), style, format, context, bibEntryTypesManager).getFirst();
105121
assertEquals(expectedCitation, actualCitation);
106122
}
107123

108124
@Test
109125
void textFormat() {
110126
String expectedCitation = "[1]B. Smith, B. Jones, and J. Williams, “Title of the test entry,” BibTeX Journal, vol. 34, no. 3, pp. 45–67, Jul. 2016, doi: 10.1001/bla.blubb.\n";
111127

112-
BibEntry entry = TestEntry.getTestEntry();
113128
String style = CitationStyle.getDefault().getSource();
114129
CitationStyleOutputFormat format = CitationStyleOutputFormat.TEXT;
115130

116-
String actualCitation = CitationStyleGenerator.generateCitation(List.of(entry), style, format, new BibDatabaseContext(new BibDatabase(List.of(entry))), bibEntryTypesManager).getFirst();
131+
String actualCitation = CitationStyleGenerator.generateCitation(List.of(testEntry), style, format, context, bibEntryTypesManager).getFirst();
117132
assertEquals(expectedCitation, actualCitation);
118133
}
119134

@@ -134,12 +149,11 @@ void handleDiacritics() {
134149
@Test
135150
void handleAmpersand() {
136151
String expectedCitation = "[1]B. Smith, B. Jones, and J. Williams, “Famous quote: “&TitleTest&” - that is it,” BibTeX Journal, vol. 34, no. 3, pp. 45–67, Jul. 2016, doi: 10.1001/bla.blubb.\n";
137-
BibEntry entry = TestEntry.getTestEntry();
138-
entry.setField(StandardField.TITLE, "Famous quote: “&TitleTest&” - that is it");
152+
testEntry.setField(StandardField.TITLE, "Famous quote: “&TitleTest&” - that is it");
139153
String style = CitationStyle.getDefault().getSource();
140154
CitationStyleOutputFormat format = CitationStyleOutputFormat.TEXT;
141155

142-
String actualCitation = CitationStyleGenerator.generateCitation(List.of(entry), style, format, new BibDatabaseContext(), bibEntryTypesManager).getFirst();
156+
String actualCitation = CitationStyleGenerator.generateCitation(List.of(testEntry), style, format, context, bibEntryTypesManager).getFirst();
143157
assertEquals(expectedCitation, actualCitation);
144158
}
145159

@@ -208,7 +222,7 @@ static Stream<Arguments> cslMapping() {
208222
.withField(StandardField.ISSUE, "7")
209223
.withField(StandardField.EID, "e0270533"),
210224
"ieee.csl"),
211-
Arguments.of(
225+
Arguments.of(
212226
"[1]F. Last and J. Doe, no. 33, pp. 7–8.\n",
213227
BibDatabaseMode.BIBLATEX,
214228
new BibEntry(StandardEntryType.Article)
@@ -574,15 +588,14 @@ static Stream<Arguments> cslMapping() {
574588

575589
@ParameterizedTest
576590
@MethodSource
577-
void cslMapping(String expected, BibDatabaseMode mode, BibEntry entry, String cslFileName) throws Exception {
578-
BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(new BibDatabase(List.of(entry)));
579-
bibDatabaseContext.setMode(mode);
591+
void cslMapping(String expected, BibDatabaseMode mode, BibEntry entry, String cslFileName) {
592+
context.setMode(mode);
580593

581594
String citation = CitationStyleGenerator.generateCitation(
582595
List.of(entry),
583596
CitationStyle.createCitationStyleFromFile(cslFileName).orElseThrow().getSource(),
584597
CitationStyleOutputFormat.TEXT,
585-
bibDatabaseContext,
598+
context,
586599
bibEntryTypesManager).getFirst();
587600
assertEquals(expected, citation);
588601
}

0 commit comments

Comments
 (0)