Skip to content

Commit fe7dfbe

Browse files
authored
Merge pull request #41 from frankgrimes97/feature/ESCAPE_QUOTE_CHAR_WITH_ESCAPE_CHAR
Add CsvGenerator.Feature.ESCAPE_QUOTE_CHAR_WITH_ESCAPE_CHAR
2 parents 41e0213 + 35b1891 commit fe7dfbe

File tree

3 files changed

+69
-7
lines changed

3 files changed

+69
-7
lines changed

csv/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvGenerator.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ public enum Feature
7171
* @since 2.9
7272
*/
7373
ALWAYS_QUOTE_EMPTY_STRINGS(false),
74+
75+
/**
76+
* Feature that determines whether values written Strings (from <code>java.lang.String</code>
77+
* valued POJO properties) which contains quotes be escaped using the Schema's configured escape character instead of "".
78+
*
79+
* @since 2.9
80+
*/
81+
ESCAPE_QUOTE_CHAR_WITH_ESCAPE_CHAR(false)
7482
;
7583

7684
protected final boolean _defaultState;

csv/src/main/java/com/fasterxml/jackson/dataformat/csv/impl/CsvEncoder.java

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,11 @@ public class CsvEncoder
9090
protected boolean _cfgAlwaysQuoteStrings;
9191

9292
protected boolean _cfgAlwaysQuoteEmptyStrings;
93-
93+
94+
protected boolean _cfgEscapeQuoteCharWithEscapeChar;
95+
96+
protected final char _cfgQuoteCharEscapeChar;
97+
9498
/*
9599
/**********************************************************
96100
/* Output state
@@ -169,6 +173,7 @@ public CsvEncoder(IOContext ctxt, int csvFeatures, Writer out, CsvSchema schema)
169173
_cfgIncludeMissingTail = !CsvGenerator.Feature.OMIT_MISSING_TAIL_COLUMNS.enabledIn(_csvFeatures);
170174
_cfgAlwaysQuoteStrings = CsvGenerator.Feature.ALWAYS_QUOTE_STRINGS.enabledIn(csvFeatures);
171175
_cfgAlwaysQuoteEmptyStrings = CsvGenerator.Feature.ALWAYS_QUOTE_EMPTY_STRINGS.enabledIn(csvFeatures);
176+
_cfgEscapeQuoteCharWithEscapeChar = CsvGenerator.Feature.ESCAPE_QUOTE_CHAR_WITH_ESCAPE_CHAR.enabledIn(csvFeatures);
172177

173178
_outputBuffer = ctxt.allocConcatBuffer();
174179
_bufferRecyclable = true;
@@ -187,6 +192,12 @@ public CsvEncoder(IOContext ctxt, int csvFeatures, Writer out, CsvSchema schema)
187192
_cfgMinSafeChar = _calcSafeChar();
188193

189194
_cfgMaxQuoteCheckChars = MAX_QUOTE_CHECK;
195+
196+
_cfgQuoteCharEscapeChar = _getQuoteCharEscapeChar(
197+
_cfgEscapeQuoteCharWithEscapeChar,
198+
_cfgQuoteCharacter,
199+
_cfgEscapeCharacter
200+
);
190201
}
191202

192203
public CsvEncoder(CsvEncoder base, CsvSchema newSchema)
@@ -197,6 +208,7 @@ public CsvEncoder(CsvEncoder base, CsvSchema newSchema)
197208
_cfgIncludeMissingTail = base._cfgIncludeMissingTail;
198209
_cfgAlwaysQuoteStrings = base._cfgAlwaysQuoteStrings;
199210
_cfgAlwaysQuoteEmptyStrings = base._cfgAlwaysQuoteEmptyStrings;
211+
_cfgEscapeQuoteCharWithEscapeChar = base._cfgEscapeQuoteCharWithEscapeChar;
200212

201213
_outputBuffer = base._outputBuffer;
202214
_bufferRecyclable = base._bufferRecyclable;
@@ -212,8 +224,33 @@ public CsvEncoder(CsvEncoder base, CsvSchema newSchema)
212224
_cfgNullValue = newSchema.getNullValueOrEmpty();
213225
_cfgMinSafeChar = _calcSafeChar();
214226
_columnCount = newSchema.size();
215-
}
216-
227+
_cfgQuoteCharEscapeChar = _getQuoteCharEscapeChar(
228+
base._cfgEscapeQuoteCharWithEscapeChar,
229+
newSchema.getQuoteChar(),
230+
newSchema.getEscapeChar()
231+
);
232+
}
233+
234+
private final char _getQuoteCharEscapeChar(
235+
final boolean escapeQuoteCharWithEscapeChar,
236+
final int quoteCharacter,
237+
final int escapeCharacter) {
238+
239+
final char quoteEscapeChar;
240+
241+
if (_cfgEscapeQuoteCharWithEscapeChar && _cfgEscapeCharacter > 0) {
242+
quoteEscapeChar = (char) _cfgEscapeCharacter;
243+
}
244+
else if (_cfgQuoteCharacter > 0) {
245+
quoteEscapeChar = (char) _cfgQuoteCharacter;
246+
}
247+
else {
248+
quoteEscapeChar = '\\';
249+
}
250+
251+
return quoteEscapeChar;
252+
}
253+
217254
private final int _calcSafeChar()
218255
{
219256
// note: quote char may be -1 to signify "no quoting":
@@ -237,6 +274,7 @@ public CsvEncoder overrideFormatFeatures(int feat) {
237274
_cfgIncludeMissingTail = !CsvGenerator.Feature.OMIT_MISSING_TAIL_COLUMNS.enabledIn(feat);
238275
_cfgAlwaysQuoteStrings = CsvGenerator.Feature.ALWAYS_QUOTE_STRINGS.enabledIn(feat);
239276
_cfgAlwaysQuoteEmptyStrings = CsvGenerator.Feature.ALWAYS_QUOTE_EMPTY_STRINGS.enabledIn(feat);
277+
_cfgEscapeQuoteCharWithEscapeChar = CsvGenerator.Feature.ESCAPE_QUOTE_CHAR_WITH_ESCAPE_CHAR.enabledIn(feat);
240278
}
241279
return this;
242280
}
@@ -702,7 +740,7 @@ protected void _writeQuoted(String text, char q, int i) throws IOException
702740
if (_outputTail >= _outputEnd) {
703741
_flushBuffer();
704742
}
705-
buf[_outputTail++] = q;
743+
buf[_outputTail++] = _cfgQuoteCharEscapeChar;
706744
}
707745
if (_outputTail >= _outputEnd) {
708746
_flushBuffer();
@@ -724,7 +762,7 @@ private final void _writeLongQuoted(String text, char q) throws IOException
724762
}
725763
char c = text.charAt(i);
726764
if (c == q) { // double up
727-
_outputBuffer[_outputTail++] = q;
765+
_outputBuffer[_outputTail++] = _cfgQuoteCharEscapeChar;
728766
if (_outputTail >= _outputEnd) {
729767
_flushBuffer();
730768
}
@@ -782,7 +820,7 @@ protected void _writeQuotedAndEscaped(String text, char q, char esc, int i) thro
782820
if (_outputTail >= _outputEnd) {
783821
_flushBuffer();
784822
}
785-
buf[_outputTail++] = c;
823+
buf[_outputTail++] = (c == q) ? _cfgQuoteCharEscapeChar : c;
786824
}
787825
if (_outputTail >= _outputEnd) {
788826
_flushBuffer();
@@ -800,13 +838,14 @@ private final void _writeLongQuotedAndEscaped(String text, char esc) throws IOEx
800838
final int len = text.length();
801839
// NOTE: caller should guarantee quote char is valid (not -1) at this point:
802840
final char q = (char) _cfgQuoteCharacter;
841+
final char quoteEscape = _cfgEscapeQuoteCharWithEscapeChar ? esc : q;
803842
for (int i = 0; i < len; ++i) {
804843
if (_outputTail >= _outputEnd) {
805844
_flushBuffer();
806845
}
807846
char c = text.charAt(i);
808847
if ((c == q) || (c == esc)) { // double up, either way
809-
_outputBuffer[_outputTail++] = c;
848+
_outputBuffer[_outputTail++] = (c == q) ? quoteEscape : c;
810849
if (_outputTail >= _outputEnd) {
811850
_flushBuffer();
812851
}

csv/src/test/java/com/fasterxml/jackson/dataformat/csv/ser/TestGenerator.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,21 @@ public void testForcedQuoting60() throws Exception
204204
assertEquals("xyz,2.5\n", result);
205205
}
206206

207+
public void testForcedQuotingWithQuoteEscapedWithBackslash() throws Exception
208+
{
209+
CsvMapper mapper = mapperForCsv();
210+
CsvSchema schema = CsvSchema.builder()
211+
.addColumn("id")
212+
.addColumn("amount")
213+
.setEscapeChar('\\')
214+
.build();
215+
String result = mapper.writer(schema)
216+
.with(CsvGenerator.Feature.ALWAYS_QUOTE_STRINGS)
217+
.with(CsvGenerator.Feature.ESCAPE_QUOTE_CHAR_WITH_ESCAPE_CHAR)
218+
.writeValueAsString(new Entry("\"abc\"", 1.25));
219+
assertEquals("\"\\\"abc\\\"\",1.25\n", result);
220+
}
221+
207222
public void testForcedQuotingEmptyStrings() throws Exception
208223
{
209224
CsvMapper mapper = mapperForCsv();

0 commit comments

Comments
 (0)