From 38a5a346e64278fe82d6d4b60e2860b18b1466a9 Mon Sep 17 00:00:00 2001 From: Nirali Canopas Date: Fri, 17 Jan 2025 16:54:43 +0530 Subject: [PATCH] Added Helper function to escape CSV text --- .../Group/GroupHomeViewModelExtension.swift | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/Splito/UI/Home/Groups/Group/GroupHomeViewModelExtension.swift b/Splito/UI/Home/Groups/Group/GroupHomeViewModelExtension.swift index c337be2e..a477cc77 100644 --- a/Splito/UI/Home/Groups/Group/GroupHomeViewModelExtension.swift +++ b/Splito/UI/Home/Groups/Group/GroupHomeViewModelExtension.swift @@ -67,7 +67,7 @@ extension GroupHomeViewModel { func handleExportOptionSelection(option: ExportOptions) { let startDate = getStartDate(exportOption: option) let today = Date() - + exportTask?.cancel() exportTask = Task { [weak self] in guard let self else { return } @@ -75,12 +75,12 @@ extension GroupHomeViewModel { let expenses = try await self.expenseRepository.fetchExpenses(groupId: self.groupId, startDate: startDate, endDate: today) let transactions = try await self.transactionRepository.fetchTransactions(groupId: self.groupId, startDate: startDate, endDate: today) LogD("GroupHomeViewModel: \(#function) Expenses/payments fetched successfully.") - + if expenses.isEmpty && transactions.isEmpty { self.showToastFor(toast: ToastPrompt(type: .info, title: "No Data", message: "No data found for selected time period. Try a different time period.")) return } - + if let reportURL = await self.generateCSVReport(expenses: expenses, payments: transactions) { groupReportUrl = reportURL showShareReportSheet = true @@ -127,11 +127,14 @@ extension GroupHomeViewModel { for id in allMemberIDs { let user = await fetchMemberData(for: id) let name = user?.nameWithLastInitial ?? "Unknown" - memberNames.append(!group.members.contains(id) ? "\(name) (Removed)" : name) + memberNames.append(escapeCSV(!group.members.contains(id) ? "\(name) (Removed)" : name)) } var csvContent = "" - let header = "Date, Description, Category, Cost, Currency, \(memberNames.joined(separator: ", "))\n\n" + let headers = ["Date", "Description", "Category", "Cost", "Currency"] + .map(escapeCSV) + .joined(separator: ",") + let header = headers + "," + memberNames.joined(separator: ",") + "\n\n" csvContent += header csvContent += createExpensesSection(expenses: expenses, allMemberIDs: allMemberIDs) @@ -140,6 +143,7 @@ extension GroupHomeViewModel { do { let filePath = FileManager.default.temporaryDirectory.appendingPathComponent(GROUP_REPORT_FILE_NAME) + try FileManager.default.createDirectory(at: filePath.deletingLastPathComponent(), withIntermediateDirectories: true) try csvContent.write(to: filePath, atomically: true, encoding: .utf8) return filePath } catch { @@ -148,11 +152,20 @@ extension GroupHomeViewModel { } } + // Helper function to escape CSV text + private func escapeCSV(_ text: String) -> String { + // If text contains comma, quote, or newline, wrap in quotes and escape existing quotes + if text.contains(",") || text.contains("\"") || text.contains("\n") { + return "\"\(text.replacingOccurrences(of: "\"", with: "\"\""))\"" + } + return text + } + private func createExpensesSection(expenses: [Expense], allMemberIDs: Set) -> String { - var section = "Expenses\n" + var section = escapeCSV("Expenses") + "\n" for expense in expenses { let date = expense.date.dateValue().numericDate - var row = "\(date), \(expense.name), \(expense.category ?? "General"), \(expense.amount), \(expense.currencyCode ?? "INR")" + var row = "\(escapeCSV(date)), \(escapeCSV(expense.name)), \(escapeCSV(expense.category ?? "General")), \(expense.amount), \(escapeCSV(expense.currencyCode ?? "INR"))" // Add owe amounts for each member for member in allMemberIDs { @@ -165,12 +178,13 @@ extension GroupHomeViewModel { } private func createSettlementsSection(payments: [Transactions], allMemberIDs: Set, group: Groups) -> String { - var section = "Settlements\n" + var section = escapeCSV("Settlements") + "\n" for payment in payments { let date = payment.date.dateValue().numericDate let payer = getMemberDataBy(id: payment.payerId)?.nameWithLastInitial ?? "Unknown" let receiver = getMemberDataBy(id: payment.receiverId)?.nameWithLastInitial ?? "Unknown" - var row = "\(date), \(payer) paid \(receiver), Payment, \(payment.amount), INR" + let description = "\(escapeCSV(payer)) paid \(escapeCSV(receiver))" + var row = "\(escapeCSV(date)), \(description), Payment, \(payment.amount), INR" // Add paid amount for payer & receiver for member in allMemberIDs { @@ -184,7 +198,7 @@ extension GroupHomeViewModel { private func createTotalBalanceSection(group: Groups, allMemberIDs: Set) -> String { let date = Date() - var section = "\(date.numericDate), Total balance, , , INR" + var section = "\(escapeCSV(date.numericDate)), Total balance, , , INR" for member in allMemberIDs { if let memberBalance = group.balances.first(where: { $0.id == member }) {