From 21cdbdaa3ead07bf2d56ed68875a3b1cc2d78e54 Mon Sep 17 00:00:00 2001
From: Robert Goldmann <deadlocker@gmx.de>
Date: Sun, 8 Jan 2023 15:06:00 +0100
Subject: [PATCH] #724 - new input to configure encoding

---
 .../TransactionImportController.java          |  9 ++++--
 .../transactions/csvImport/CsvImport.java     | 31 +++++++++++++------
 .../transactions/csvImport/CsvParser.java     |  8 ++++-
 .../resources/languages/base_de.properties    |  2 ++
 .../resources/languages/base_en.properties    |  2 ++
 .../transactions/transactionImport.ftl        |  6 +++-
 6 files changed, 45 insertions(+), 13 deletions(-)

diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportController.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportController.java
index a5b20376b..bd88f02cd 100644
--- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportController.java
+++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportController.java
@@ -58,7 +58,7 @@ public class TransactionImportController extends BaseController
 	@GetMapping
 	public String transactionImport(HttpServletRequest request, Model model)
 	{
-		model.addAttribute(RequestAttributeNames.CSV_IMPORT, new CsvImport(null, ";"));
+		model.addAttribute(RequestAttributeNames.CSV_IMPORT, new CsvImport(null, ";", StandardCharsets.UTF_8.name()));
 		return ReturnValues.TRANSACTION_IMPORT;
 	}
 
@@ -78,6 +78,11 @@ public class TransactionImportController extends BaseController
 			bindingResult.addError(new FieldError("CsvImport", "separator", "", false, new String[]{"warning.transaction.import.separator"}, null, null));
 		}
 
+		if(!csvImport.isEncodingSupported())
+		{
+			bindingResult.addError(new FieldError("CsvImport", "encoding", "", false, new String[]{"warning.transaction.import.encoding"}, null, null));
+		}
+
 		if(bindingResult.hasErrors())
 		{
 			model.addAttribute(ModelAttributes.ERROR, bindingResult);
@@ -87,7 +92,7 @@ public class TransactionImportController extends BaseController
 
 		try
 		{
-			final String csvString = new String(csvImport.file().getBytes(), StandardCharsets.UTF_8);
+			final String csvString = new String(csvImport.file().getBytes(), csvImport.encoding());
 			final List<CsvRow> csvRows = CsvParser.parseCsv(csvString, csvImport.separator().charAt(0));
 
 			request.setAttribute(RequestAttributeNames.CSV_IMPORT, csvImport, RequestAttributes.SCOPE_SESSION);
diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvImport/CsvImport.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvImport/CsvImport.java
index c5e944900..2fd561af1 100644
--- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvImport/CsvImport.java
+++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvImport/CsvImport.java
@@ -1,17 +1,15 @@
 package de.deadlocker8.budgetmaster.transactions.csvImport;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.web.multipart.MultipartFile;
 
-public record CsvImport(MultipartFile file, String separator)
+import java.nio.charset.Charset;
+import java.text.MessageFormat;
+
+public record CsvImport(MultipartFile file, String separator, String encoding)
 {
-	@Override
-	public String toString()
-	{
-		return "CsvImport{" +
-				"file=" + file +
-				", separator='" + separator + '\'' +
-				'}';
-	}
+	private static final Logger LOGGER = LoggerFactory.getLogger(CsvImport.class);
 
 	public boolean isValidSeparator()
 	{
@@ -23,6 +21,21 @@ public record CsvImport(MultipartFile file, String separator)
 		return separator.strip().length() == 1;
 	}
 
+	public boolean isEncodingSupported()
+	{
+		try
+		{
+			Charset.forName(encoding);
+			return true;
+		}
+		catch(IllegalArgumentException e)
+		{
+			LOGGER.error(MessageFormat.format("Could not create charset from encoding name: {0}", encoding), e);
+		}
+
+		return false;
+	}
+
 	public String getFileName()
 	{
 		if(file == null)
diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvImport/CsvParser.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvImport/CsvParser.java
index b0b88188b..98098d25b 100644
--- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvImport/CsvParser.java
+++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvImport/CsvParser.java
@@ -13,11 +13,17 @@ import java.util.List;
 
 public class CsvParser
 {
+	private CsvParser()
+	{
+	}
+
 	public static List<CsvRow> parseCsv(String csvString, char separator) throws IOException, CsvValidationException
 	{
 		final ArrayList<CsvRow> csvRows = new ArrayList<>();
 
-		final CSVParser csvParser = new CSVParserBuilder().withSeparator(separator).build();
+		final CSVParser csvParser = new CSVParserBuilder()
+				.withSeparator(separator)
+				.build();
 
 		try(CSVReader reader = new CSVReaderBuilder(
 				new StringReader(csvString))
diff --git a/BudgetMasterServer/src/main/resources/languages/base_de.properties b/BudgetMasterServer/src/main/resources/languages/base_de.properties
index 1f0e210fd..905dd650b 100644
--- a/BudgetMasterServer/src/main/resources/languages/base_de.properties
+++ b/BudgetMasterServer/src/main/resources/languages/base_de.properties
@@ -233,6 +233,7 @@ warning.empty.git.branch.name=Bitte gib den Namen des git-Branches ein.
 warning.empty.git.user.name=Bitte gib deinen git-Nutzernamen ein.
 warning.empty.git.token=Bitte gib dein git-Zugriffstoken ein.
 warning.transaction.import.separator=Ungültiges Trennzeichen. Bitte genau ein Zeichen eingeben.
+warning.transaction.import.encoding=Ungültige oder nicht unterstützte Kodierung.
 
 
 # UI
@@ -372,6 +373,7 @@ transactions.recurring.placeholder=Keine aktiven wiederholenden Buchungen
 transactions.import.overview=Übersicht
 transactions.import.column=Spalte
 transactions.import.separator=Trennzeichen
+transactions.import.encoding=Kodierung
 
 repeating.button.add=Wiederholung hinzufügen
 repeating.button.remove=Wiederholung entfernen
diff --git a/BudgetMasterServer/src/main/resources/languages/base_en.properties b/BudgetMasterServer/src/main/resources/languages/base_en.properties
index 9d7e57ce5..c3733f01d 100644
--- a/BudgetMasterServer/src/main/resources/languages/base_en.properties
+++ b/BudgetMasterServer/src/main/resources/languages/base_en.properties
@@ -233,6 +233,7 @@ warning.empty.git.branch.name=Please insert the git branch name.
 warning.empty.git.user.name=Please insert your git username.
 warning.empty.git.token=Please insert your git access token.
 warning.transaction.import.separator=Invalid separator. Please enter exactly one character.
+warning.transaction.import.encoding=Invalid or unsupported encoding.
 
 
 # UI
@@ -371,6 +372,7 @@ transactions.recurring.placeholder=No active recurring transactions
 transactions.import.overview=Overview
 transactions.import.column=Column
 transactions.import.separator=Separator
+transactions.import.encoding=Encoding
 
 repeating.button.add=Add repetition
 repeating.button.remove=Remove repetition
diff --git a/BudgetMasterServer/src/main/resources/templates/transactions/transactionImport.ftl b/BudgetMasterServer/src/main/resources/templates/transactions/transactionImport.ftl
index cd7c01f51..84cde3554 100644
--- a/BudgetMasterServer/src/main/resources/templates/transactions/transactionImport.ftl
+++ b/BudgetMasterServer/src/main/resources/templates/transactions/transactionImport.ftl
@@ -69,10 +69,14 @@
         </div>
 
         <div class="row">
-            <div class="input-field col s2 offset-s5">
+            <div class="input-field col s2 offset-s4">
                 <input id="separator" type="text" name="separator" <@validation.validation "separator" "center-align"/> value="<#if csvImport??>${csvImport.separator()}</#if>">
                 <label class="input-label" for="separator">${locale.getString("transactions.import.separator")}</label>
             </div>
+            <div class="input-field col s2">
+                <input id="encoding" type="text" name="encoding"" <@validation.validation "encoding" "center-align"/> value="<#if csvImport??>${csvImport.encoding()?upper_case}</#if>">
+                <label class="input-label" for="encoding">${locale.getString("transactions.import.encoding")}</label>
+            </div>
         </div>
 
         <div class="row">
-- 
GitLab