Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove ampersand escape when writing to bib file #5869

Merged
merged 3 commits into from
Jan 25, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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