Skip to content

Commit

Permalink
Remove ampersand escape when writing to bib file (#5869)
Browse files Browse the repository at this point in the history
* Remove ampersand escape when writing to bib file

Instead use save action to handle this case. Includes also a bit of refactoring (mostly renames).

* Add missing localization

* Update JabRef_en.properties
  • Loading branch information
tobiasdiez authored Jan 25, 2020
1 parent 24420e7 commit 0407d9a
Show file tree
Hide file tree
Showing 25 changed files with 217 additions and 150 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `#
- We fixed an issue where the file extension was missing after downloading a file (we now fall-back to pdf). [#5816](https://github.com/JabRef/jabref/issues/5816)

### Removed
- Ampersands are no longer escaped by default in the `bib` file. If you want to keep the current behaviour, you can use the new "Escape Ampersands" formatter as a save action.


## [5.0-beta] – 2019-12-15
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,23 @@
* in JabRef style. The reformatting must undo all formatting done by JabRef when
* writing the same fields.
*/
public class FieldContentParser {
public class FieldContentFormatter {

// 's' matches a space, tab, new line, carriage return.
private static final Pattern WHITESPACE = Pattern.compile("\\s+");

private final Set<Field> multiLineFields;


public FieldContentParser(FieldContentParserPreferences prefs) {
Objects.requireNonNull(prefs);
public FieldContentFormatter(FieldContentFormatterPreferences preferences) {
Objects.requireNonNull(preferences);

multiLineFields = new HashSet<>();
// the following two are also coded in org.jabref.logic.bibtex.LatexFieldFormatter.format(String, String)
multiLineFields.add(StandardField.ABSTRACT);
multiLineFields.add(StandardField.COMMENT);
multiLineFields.add(StandardField.REVIEW);
// the file field should not be formatted, therefore we treat it as a multi line field
multiLineFields.addAll(prefs.getNonWrappableFields());
multiLineFields.addAll(preferences.getNonWrappableFields());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@

import org.jabref.model.entry.field.Field;

public class FieldContentParserPreferences {
public class FieldContentFormatterPreferences {

private final List<Field> nonWrappableFields;

public FieldContentParserPreferences() {
public FieldContentFormatterPreferences() {
// This constructor is only to allow an empty constructor in SavePreferences
this.nonWrappableFields = Collections.emptyList();
}

public FieldContentParserPreferences(List<Field> nonWrappableFields) {
public FieldContentFormatterPreferences(List<Field> nonWrappableFields) {
this.nonWrappableFields = nonWrappableFields;
}

Expand Down
82 changes: 13 additions & 69 deletions src/main/java/org/jabref/logic/bibtex/FieldWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ public class FieldWriter {
private static final char FIELD_START = '{';
private static final char FIELD_END = '}';
private final boolean neverFailOnHashes;
private final FieldWriterPreferences prefs;
private final FieldContentParser parser;
private final FieldWriterPreferences preferences;
private final FieldContentFormatter formatter;
private StringBuilder stringBuilder;

public FieldWriter(FieldWriterPreferences prefs) {
this(true, prefs);
public FieldWriter(FieldWriterPreferences preferences) {
this(true, preferences);
}

private FieldWriter(boolean neverFailOnHashes, FieldWriterPreferences prefs) {
private FieldWriter(boolean neverFailOnHashes, FieldWriterPreferences preferences) {
this.neverFailOnHashes = neverFailOnHashes;
this.prefs = prefs;
this.preferences = preferences;

parser = new FieldContentParser(prefs.getFieldContentParserPreferences());
formatter = new FieldContentFormatter(preferences.getFieldContentFormatterPreferences());
}

public static FieldWriter buildIgnoreHashes(FieldWriterPreferences prefs) {
Expand Down Expand Up @@ -152,13 +152,13 @@ private String formatAndResolveStrings(String content, Field field) throws Inval
}
}

return parser.format(stringBuilder, field);
return formatter.format(stringBuilder, field);
}

private boolean shouldResolveStrings(Field field) {
if (prefs.isResolveStringsAllFields()) {
if (preferences.isResolveStringsAllFields()) {
// Resolve strings for all fields except some:
return !prefs.getDoNotResolveStringsFor().contains(field);
return !preferences.getDoNotResolveStringsFor().contains(field);
} else {
// Default operation - we only resolve strings for standard fields:
return field instanceof StandardField || InternalField.BIBTEX_STRING.equals(field);
Expand All @@ -170,7 +170,7 @@ private String formatWithoutResolvingStrings(String content, Field field) throws

stringBuilder = new StringBuilder(String.valueOf(FIELD_START));

stringBuilder.append(parser.format(content, field));
stringBuilder.append(formatter.format(content, field));

stringBuilder.append(FIELD_END);

Expand All @@ -179,63 +179,7 @@ private String formatWithoutResolvingStrings(String content, Field field) throws

private void writeText(String text, int startPos, int endPos) {
stringBuilder.append(FIELD_START);
boolean escape = false;
boolean inCommandName = false;
boolean inCommand = false;
boolean inCommandOption = false;
int nestedEnvironments = 0;
StringBuilder commandName = new StringBuilder();
for (int i = startPos; i < endPos; i++) {
char c = text.charAt(i);

// Track whether we are in a LaTeX command of some sort.
if (Character.isLetter(c) && (escape || inCommandName)) {
inCommandName = true;
if (!inCommandOption) {
commandName.append(c);
}
} else if (Character.isWhitespace(c) && (inCommand || inCommandOption)) {
// Whitespace
} else if (inCommandName) {
// This means the command name is ended.
// Perhaps the beginning of an argument:
if (c == '[') {
inCommandOption = true;
} else if (inCommandOption && (c == ']')) {
// Or the end of an argument:
inCommandOption = false;
} else if (!inCommandOption && (c == '{')) {
inCommandName = false;
inCommand = true;
} else {
// Or simply the end of this command alltogether:
commandName.delete(0, commandName.length());
inCommandName = false;
}
}
// If we are in a command body, see if it has ended:
if (inCommand && (c == '}')) {
if ("begin".equals(commandName.toString())) {
nestedEnvironments++;
}
if ((nestedEnvironments > 0) && "end".equals(commandName.toString())) {
nestedEnvironments--;
}

commandName.delete(0, commandName.length());
inCommand = false;
}

// We add a backslash before any ampersand characters, with one exception: if
// we are inside an \\url{...} command, we should write it as it is. Maybe.
if ((c == '&') && !escape && !(inCommand && "url".equals(commandName.toString()))
&& (nestedEnvironments == 0)) {
stringBuilder.append("\\&");
} else {
stringBuilder.append(c);
}
escape = c == '\\';
}
stringBuilder.append(text, startPos, endPos);
stringBuilder.append(FIELD_END);
}

Expand All @@ -245,7 +189,7 @@ private void writeStringLabel(String text, int startPos, int endPos, boolean fir
}

private void putIn(String s) {
stringBuilder.append(StringUtil.wrap(s, prefs.getLineLength(), OS.NEWLINE));
stringBuilder.append(StringUtil.wrap(s, preferences.getLineLength(), OS.NEWLINE));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ public class FieldWriterPreferences {
private final boolean resolveStringsAllFields;
private final List<Field> doNotResolveStringsFor;
private final int lineLength = 65; // Constant
private final FieldContentParserPreferences fieldContentParserPreferences;
private final FieldContentFormatterPreferences fieldContentFormatterPreferences;

public FieldWriterPreferences(boolean resolveStringsAllFields, List<Field> doNotResolveStringsFor,
FieldContentParserPreferences fieldContentParserPreferences) {
FieldContentFormatterPreferences fieldContentFormatterPreferences) {
this.resolveStringsAllFields = resolveStringsAllFields;
this.doNotResolveStringsFor = doNotResolveStringsFor;
this.fieldContentParserPreferences = fieldContentParserPreferences;
this.fieldContentFormatterPreferences = fieldContentFormatterPreferences;
}

public FieldWriterPreferences() {
// This constructor is only to allow an empty constructor in SavePreferences
this(true, Collections.emptyList(), new FieldContentParserPreferences());
this(true, Collections.emptyList(), new FieldContentFormatterPreferences());
}

public boolean isResolveStringsAllFields() {
Expand All @@ -36,7 +36,7 @@ public int getLineLength() {
return lineLength;
}

public FieldContentParserPreferences getFieldContentParserPreferences() {
return fieldContentParserPreferences;
public FieldContentFormatterPreferences getFieldContentFormatterPreferences() {
return fieldContentFormatterPreferences;
}
}
2 changes: 2 additions & 0 deletions src/main/java/org/jabref/logic/formatter/Formatters.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import org.jabref.logic.formatter.bibtexfields.CleanupUrlFormatter;
import org.jabref.logic.formatter.bibtexfields.ClearFormatter;
import org.jabref.logic.formatter.bibtexfields.EscapeAmpersandsFormatter;
import org.jabref.logic.formatter.bibtexfields.EscapeUnderscoresFormatter;
import org.jabref.logic.formatter.bibtexfields.HtmlToLatexFormatter;
import org.jabref.logic.formatter.bibtexfields.HtmlToUnicodeFormatter;
Expand Down Expand Up @@ -69,6 +70,7 @@ public static List<Formatter> getOthers() {
new RemoveBracesFormatter(),
new UnitsToLatexFormatter(),
new EscapeUnderscoresFormatter(),
new EscapeAmpersandsFormatter(),
new ShortenDOIFormatter()
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package org.jabref.logic.formatter.bibtexfields;

import java.util.Objects;

import org.jabref.logic.l10n.Localization;
import org.jabref.model.cleanup.Formatter;

public class EscapeAmpersandsFormatter extends Formatter {

@Override
public String getName() {
return Localization.lang("Escape ampersands");
}

@Override
public String getKey() {
return "escapeAmpersands";
}

@Override
public String format(String value) {
Objects.requireNonNull(value);

StringBuilder result = new StringBuilder();

boolean escape = false;
boolean inCommandName = false;
boolean inCommand = false;
boolean inCommandOption = false;
int nestedEnvironments = 0;
StringBuilder commandName = new StringBuilder();
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);

// Track whether we are in a LaTeX command of some sort.
if (Character.isLetter(c) && (escape || inCommandName)) {
inCommandName = true;
if (!inCommandOption) {
commandName.append(c);
}
} else if (Character.isWhitespace(c) && (inCommand || inCommandOption)) {
// Whitespace
} else if (inCommandName) {
// This means the command name is ended.
// Perhaps the beginning of an argument:
if (c == '[') {
inCommandOption = true;
} else if (inCommandOption && (c == ']')) {
// Or the end of an argument:
inCommandOption = false;
} else if (!inCommandOption && (c == '{')) {
inCommandName = false;
inCommand = true;
} else {
// Or simply the end of this command alltogether:
commandName.delete(0, commandName.length());
inCommandName = false;
}
}
// If we are in a command body, see if it has ended:
if (inCommand && (c == '}')) {
if ("begin".equals(commandName.toString())) {
nestedEnvironments++;
}
if ((nestedEnvironments > 0) && "end".equals(commandName.toString())) {
nestedEnvironments--;
}

commandName.delete(0, commandName.length());
inCommand = false;
}

// We add a backslash before any ampersand characters, with one exception: if
// we are inside an \\url{...} command, we should write it as it is. Maybe.
if ((c == '&') && !escape && !(inCommand && "url".equals(commandName.toString()))
&& (nestedEnvironments == 0)) {
result.append("\\&");
} else {
result.append(c);
}
escape = c == '\\';
}
return result.toString();
}

@Override
public String getDescription() {
return Localization.lang("Escape ampersands");
}

@Override
public String getExampleInput() {
return "Text & with &ampersands";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import java.nio.charset.Charset;
import java.util.Set;

import org.jabref.logic.bibtex.FieldContentParserPreferences;
import org.jabref.logic.bibtex.FieldContentFormatterPreferences;
import org.jabref.logic.bibtexkeypattern.BibtexKeyPatternPreferences;
import org.jabref.logic.importer.fileformat.CustomImporter;
import org.jabref.logic.xmp.XmpPreferences;
Expand All @@ -14,18 +14,18 @@ public class ImportFormatPreferences {
private final Charset encoding;
private final Character keywordSeparator;
private final BibtexKeyPatternPreferences bibtexKeyPatternPreferences;
private final FieldContentParserPreferences fieldContentParserPreferences;
private final FieldContentFormatterPreferences fieldContentFormatterPreferences;
private final XmpPreferences xmpPreferences;
private final boolean keywordSyncEnabled;

public ImportFormatPreferences(Set<CustomImporter> customImportList, Charset encoding, Character keywordSeparator,
BibtexKeyPatternPreferences bibtexKeyPatternPreferences,
FieldContentParserPreferences fieldContentParserPreferences, XmpPreferences xmpPreferences, boolean keywordSyncEnabled) {
BibtexKeyPatternPreferences bibtexKeyPatternPreferences,
FieldContentFormatterPreferences fieldContentFormatterPreferences, XmpPreferences xmpPreferences, boolean keywordSyncEnabled) {
this.customImportList = customImportList;
this.encoding = encoding;
this.keywordSeparator = keywordSeparator;
this.bibtexKeyPatternPreferences = bibtexKeyPatternPreferences;
this.fieldContentParserPreferences = fieldContentParserPreferences;
this.fieldContentFormatterPreferences = fieldContentFormatterPreferences;
this.xmpPreferences = xmpPreferences;
this.keywordSyncEnabled = keywordSyncEnabled;
}
Expand All @@ -50,13 +50,13 @@ public BibtexKeyPatternPreferences getBibtexKeyPatternPreferences() {
return bibtexKeyPatternPreferences;
}

public FieldContentParserPreferences getFieldContentParserPreferences() {
return fieldContentParserPreferences;
public FieldContentFormatterPreferences getFieldContentFormatterPreferences() {
return fieldContentFormatterPreferences;
}

public ImportFormatPreferences withEncoding(Charset newEncoding) {
return new ImportFormatPreferences(customImportList, newEncoding, keywordSeparator, bibtexKeyPatternPreferences,
fieldContentParserPreferences, xmpPreferences, keywordSyncEnabled);
fieldContentFormatterPreferences, xmpPreferences, keywordSyncEnabled);
}

/**
Expand Down
Loading

0 comments on commit 0407d9a

Please sign in to comment.