dbf-exporter fix broken symbol: replace unsupported character on whitespace (см. также параметр export-dbf-service.common.encoding)

This commit is contained in:
AKurakin 2024-07-09 18:24:53 +03:00
parent eceb681861
commit 2760ddc7d3
3 changed files with 155 additions and 1 deletions

View file

@ -0,0 +1,47 @@
package ru.spcex.clearing.dbf.exporter.logic;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.Objects;
/**
* При перекодировании текста в кодировку, которая не поддерживает символы - они заменяются на знак "?".
* Надо чтобы они заменялись не на "?", а на пробел.
*/
public class CharsetFilter {
final Charset charset;
public CharsetFilter(Charset dbfCharset) {
this.charset = dbfCharset;
Objects.requireNonNull(charset);
}
public String filterUnsupportedChars(String text) {
if (text == null || text.isEmpty())
return text;
CharBuffer textBuf = CharBuffer.wrap(text);
ByteBuffer bBuffer = charset.encode(textBuf);
CharBuffer cBuffer = charset.decode(bBuffer);
if (cBuffer.equals(textBuf)) {
return text;
} else {
char[] arr = text.toCharArray();
if (arr.length != cBuffer.length())
return text; // never
for (int i = 0; i < arr.length; i++) {
if (cBuffer.get(i) != arr[i]) {
arr[i] = ' ';
}
}
return String.valueOf(arr);
}
/* Можно было так:
sun.nio.cs.ThreadLocalCoders.encoderFor(charset)
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE)
.encode(textBuf);
но для этого надо добавить '--add-exports java.base/sun.nio.cs=ALL-UNNAMED' в опции*/
}
}

View file

@ -16,6 +16,7 @@ import org.springframework.stereotype.Component;
import ru.spcex.clearing.dbf.exporter.config.SFTPConfig;
import ru.spcex.clearing.dbf.exporter.config.settings.ExportDBFServiceSettings;
import ru.spcex.clearing.dbf.exporter.exceptions.ConfigException;
import ru.spcex.clearing.dbf.exporter.logic.CharsetFilter;
import ru.spcex.clearing.dbf.exporter.logic.data.ResultContainer;
import ru.spcex.clearing.dbf.exporter.logic.data.enums.StageResult;
import ru.spcex.clearing.dbf.exporter.logic.data.enums.Table;
@ -68,6 +69,7 @@ public class ExportFromHazelcast extends Stage implements InitializingBean {
String currency = null;
String path = null;
try (DBFWriter dbfWriter = new DBFWriter(dbfFile, dbfCharset)) {
CharsetFilter charsetFilter = new CharsetFilter(dbfWriter.getCharset());
Collection<? extends SpcexObjectBase> tableRows;
if (resultContainer.getGroupId() != null) {
Map<String, Long> queryParams = Map.of("generationId", resultContainer.getGroupId());
@ -95,7 +97,13 @@ public class ExportFromHazelcast extends Stage implements InitializingBean {
currency = currentConverter.getCurrency(value);
dbfWriter.setFields(currentConverter.getDBFHeaders());
}
dbfWriter.addRecord(currentConverter.toObjectArray(value));
Object[] rawRow = currentConverter.toObjectArray(value);
for (int i = 0; i < rawRow.length; i++) {
if (rawRow[i] instanceof String rowText) {
rawRow[i] = charsetFilter.filterUnsupportedChars(rowText);
}
}
dbfWriter.addRecord(rawRow);
}
if (CurrencyCode.isRub(currency))
path = settings.getStore().getOutPayValDir().get(ruCurrency);

View file

@ -0,0 +1,99 @@
package ru.spcex.clearing.dbf.exporter.logic;
import org.junit.jupiter.api.Test;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import static org.junit.jupiter.api.Assertions.*;
class CharsetFilterTest {
@Test
void filterUnsupportedChars_utf8() {
CharsetFilter filter = new CharsetFilter(Charset.forName("utf-8"));
assertEquals(null, filter.filterUnsupportedChars(null));
assertEquals("", filter.filterUnsupportedChars(""));
String empty = "";
assertSame(empty, filter.filterUnsupportedChars(empty));
assertEquals("assertEquals(\"\", filter.filterUnsupportedChars(\"\"));", filter.filterUnsupportedChars("assertEquals(\"\", filter.filterUnsupportedChars(\"\"));"));
assertEquals("Test обычные АБВЭЮЯёЁ ABCXYZ", filter.filterUnsupportedChars("Test обычные АБВЭЮЯёЁ ABCXYZ"));
assertEquals("/*-+:;'\\/.,~!@#$%^&*()_+`1234567890-=", filter.filterUnsupportedChars("/*-+:;'\\/.,~!@#$%^&*()_+`1234567890-="));
assertEquals(" 0123456789/*-+:;,.'\"\\()qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮйцукенгшщзхъфывапролджэячсмитьбю.",
filter.filterUnsupportedChars(" 0123456789/*-+:;,.'\"\\()qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮйцукенгшщзхъфывапролджэячсмитьбю."));
assertEquals("Акционерный «Абсо\nлют» ", filter.filterUnsupportedChars("Акционерный «Абсо\nлют» "));
assertEquals("К«а»вычки '\" и п\160роб\u2060е л\u8199ы. И иные символы: …„‘\n" +
"2018 \t\n2019 \t“\n201C \t”\n201D \t•\n2022 \t\n2013 \t—\n2014 \t\t™\n2122 \\n459 \t",
filter.filterUnsupportedChars("К«а»вычки '\" и п\160роб\u2060е л\u8199ы. И иные символы: …„‘\n" +
"2018 \t\n2019 \t“\n201C \t”\n201D \t•\n2022 \t\n2013 \t—\n2014 \t\t™\n2122 \\n459 \t"));
}
@Test
void filterUnsupportedChars_windows1251() {
CharsetFilter filter = new CharsetFilter(Charset.forName("windows-1251"));
assertEquals(null, filter.filterUnsupportedChars(null));
assertEquals("", filter.filterUnsupportedChars(""));
String empty = "";
assertSame(empty, filter.filterUnsupportedChars(empty));
assertEquals("assertEquals(\"\", filter.filterUnsupportedChars(\"\"));", filter.filterUnsupportedChars("assertEquals(\"\", filter.filterUnsupportedChars(\"\"));"));
assertEquals("Test обычные АБВЭЮЯёЁ ABCXYZ", filter.filterUnsupportedChars("Test обычные АБВЭЮЯёЁ ABCXYZ"));
assertEquals("/*-+:;'\\/.,~!@#$%^&*()_+`1234567890-=", filter.filterUnsupportedChars("/*-+:;'\\/.,~!@#$%^&*()_+`1234567890-="));
assertEquals(" 0123456789/*-+:;,.'\"\\()qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮйцукенгшщзхъфывапролджэячсмитьбю.",
filter.filterUnsupportedChars(" 0123456789/*-+:;,.'\"\\()qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮйцукенгшщзхъфывапролджэячсмитьбю."));
assertEquals("Акционерный «Абсо\nлют» ", filter.filterUnsupportedChars("Акционерный «Абсо\nлют» "));
assertEquals("К«а»вычки '\" и пpр о б е л ы. И иные символы: …„‘\n" +
"2018 \t\n2019 \t“\n201C \t”\n201D \t•\n2022 \t\n2013 \t—\n2014 \t\t™\n2122 \\n459 \t",
filter.filterUnsupportedChars("К«а»вычки '\" и п\160роб\u2060е л\u8199ы. И иные символы: …„‘\n" +
"2018 \t\n2019 \t“\n201C \t”\n201D \t•\n2022 \t\n2013 \t—\n2014 \t\t™\n2122 \\n459 \t"));
}
@Test
void filterUnsupportedChars_cp866() {
CharsetFilter filter = new CharsetFilter(Charset.forName("cp866"));
assertEquals(null, filter.filterUnsupportedChars(null));
assertEquals("", filter.filterUnsupportedChars(""));
String empty = "";
assertSame(empty, filter.filterUnsupportedChars(empty));
assertEquals("assertEquals(\"\", filter.filterUnsupportedChars(\"\"));", filter.filterUnsupportedChars("assertEquals(\"\", filter.filterUnsupportedChars(\"\"));"));
assertEquals("Test обычные АБВЭЮЯёЁ ABCXYZ", filter.filterUnsupportedChars("Test обычные АБВЭЮЯёЁ ABCXYZ"));
assertEquals("/*-+:;'\\/.,~!@#$%^&*()_+`1234567890-=", filter.filterUnsupportedChars("/*-+:;'\\/.,~!@#$%^&*()_+`1234567890-="));
assertEquals(" 0123456789/*-+:;,.'\"\\()qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮйцукенгшщзхъфывапролджэячсмитьбю.",
filter.filterUnsupportedChars(" 0123456789/*-+:;,.'\"\\()qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮйцукенгшщзхъфывапролджэячсмитьбю."));
assertEquals("Акционерный Абсо\nлют ", filter.filterUnsupportedChars("Акционерный «Абсо\nлют» "));
assertEquals("К а вычки '\" и пpр о б е л ы. И иные символы: \n" +
"2018 \t \n2019 \t \n201C \t \n201D \t \n2022 \t \n2013 \t \n2014 \t\t \n2122 \t \n459 \t ",
filter.filterUnsupportedChars("К«а»вычки '\" и п\160роб\u2060е л\u8199ы. И иные символы: …„‘\n" +
"2018 \t\n2019 \t“\n201C \t”\n201D \t•\n2022 \t\n2013 \t—\n2014 \t\t™\n2122 \\n459 \t"));
}
@Test
void canCorrectEncode() throws UnsupportedEncodingException {
String sample = " 0123456789/*-+:;,. .,~!@#$%^&*()_+` '\"\\(…„‘) К«а»в’ы“ч™кљи п\160роб\u2060е л\u8199ы qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮйцукенгшщзхъфывапролджэячсмитьбю.ёЁ";
sample = "Test aaabbbccc АААБББВВВЯЯЯяяяЁ: " + sample + sample + sample;
for (String charset : new String[]{"utf-8", "windows-1251", "cp866"}) {
CharsetFilter filter = new CharsetFilter(Charset.forName(charset));
String toEnc = filter.filterUnsupportedChars(sample);
byte[] b = toEnc.getBytes(charset);
String returned = new String(b, charset);
assertEquals(toEnc, returned, charset);
}
}
/**
* Кодировка не используется, но она применяется по умолчанию в библиотеке DBFStandardCharsets
*/
@Test
void filterUnsupportedChars_ISO_8859_1() {
CharsetFilter filter = new CharsetFilter(Charset.forName("ISO_8859_1")); //DBFStandardCharsets.ISO_8859_1
assertEquals(null, filter.filterUnsupportedChars(null));
assertEquals("", filter.filterUnsupportedChars(""));
assertEquals("assertEquals(\"\", filter.filterUnsupportedChars(\"\"));", filter.filterUnsupportedChars("assertEquals(\"\", filter.filterUnsupportedChars(\"\"));"));
assertEquals("Test ABCXYZ", filter.filterUnsupportedChars("Test обычные АБВЭЮЯёЁ ABCXYZ"));
assertEquals("/*-+:;'\\/.,~!@#$%^&*()_+`1234567890-=", filter.filterUnsupportedChars("/*-+:;'\\/.,~!@#$%^&*()_+`1234567890-="));
assertEquals(" 0123456789/*-+:;,.'\"\\()qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM .",
filter.filterUnsupportedChars(" 0123456789/*-+:;,.'\"\\()qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮйцукенгшщзхъфывапролджэячсмитьбю."));
assertEquals(" « \n » ", filter.filterUnsupportedChars("Акционерный «Абсо\nлют» "));
}
}