|
| 1 | +// |
| 2 | +// ClipboardExporter.swift |
| 3 | +// TableProMobile |
| 4 | +// |
| 5 | + |
| 6 | +import Foundation |
| 7 | +import TableProModels |
| 8 | +import UIKit |
| 9 | + |
| 10 | +enum ExportFormat: String, CaseIterable, Identifiable { |
| 11 | + case json = "JSON" |
| 12 | + case csv = "CSV" |
| 13 | + case sqlInsert = "SQL INSERT" |
| 14 | + var id: String { rawValue } |
| 15 | +} |
| 16 | + |
| 17 | +enum ClipboardExporter { |
| 18 | + static func exportRow(columns: [ColumnInfo], row: [String?], format: ExportFormat, tableName: String? = nil) -> String { |
| 19 | + switch format { |
| 20 | + case .json: |
| 21 | + return rowToJson(columns: columns, row: row) |
| 22 | + case .csv: |
| 23 | + return rowToCsv(columns: columns, row: row, includeHeader: true) |
| 24 | + case .sqlInsert: |
| 25 | + return rowToInsert(columns: columns, row: row, tableName: tableName ?? "table") |
| 26 | + } |
| 27 | + } |
| 28 | + |
| 29 | + static func exportRows(columns: [ColumnInfo], rows: [[String?]], format: ExportFormat, tableName: String? = nil) -> String { |
| 30 | + switch format { |
| 31 | + case .json: |
| 32 | + let objects = rows.map { rowToJson(columns: columns, row: $0) } |
| 33 | + return "[\n" + objects.joined(separator: ",\n") + "\n]" |
| 34 | + case .csv: |
| 35 | + let header = columns.map { escapeCsvField($0.name) }.joined(separator: ",") |
| 36 | + let dataLines = rows.map { row in |
| 37 | + columns.indices.map { i in |
| 38 | + escapeCsvField(i < row.count ? row[i] ?? "NULL" : "NULL") |
| 39 | + }.joined(separator: ",") |
| 40 | + } |
| 41 | + return ([header] + dataLines).joined(separator: "\n") |
| 42 | + case .sqlInsert: |
| 43 | + let name = tableName ?? "table" |
| 44 | + return rows.map { rowToInsert(columns: columns, row: $0, tableName: name) }.joined(separator: "\n") |
| 45 | + } |
| 46 | + } |
| 47 | + |
| 48 | + static func copyToClipboard(_ text: String) { |
| 49 | + UIPasteboard.general.string = text |
| 50 | + } |
| 51 | + |
| 52 | + // MARK: - Private |
| 53 | + |
| 54 | + private static func rowToJson(columns: [ColumnInfo], row: [String?]) -> String { |
| 55 | + var pairs: [String] = [] |
| 56 | + for (i, col) in columns.enumerated() { |
| 57 | + let value = i < row.count ? row[i] : nil |
| 58 | + let key = " \"\(escapeJsonString(col.name))\"" |
| 59 | + if let value { |
| 60 | + if Int64(value) != nil || Double(value) != nil { |
| 61 | + pairs.append("\(key): \(value)") |
| 62 | + } else if value == "true" || value == "false" { |
| 63 | + pairs.append("\(key): \(value)") |
| 64 | + } else { |
| 65 | + pairs.append("\(key): \"\(escapeJsonString(value))\"") |
| 66 | + } |
| 67 | + } else { |
| 68 | + pairs.append("\(key): null") |
| 69 | + } |
| 70 | + } |
| 71 | + return "{\n" + pairs.joined(separator: ",\n") + "\n}" |
| 72 | + } |
| 73 | + |
| 74 | + private static func rowToCsv(columns: [ColumnInfo], row: [String?], includeHeader: Bool) -> String { |
| 75 | + var lines: [String] = [] |
| 76 | + if includeHeader { |
| 77 | + lines.append(columns.map { escapeCsvField($0.name) }.joined(separator: ",")) |
| 78 | + } |
| 79 | + let dataLine = columns.indices.map { i in |
| 80 | + escapeCsvField(i < row.count ? row[i] ?? "NULL" : "NULL") |
| 81 | + }.joined(separator: ",") |
| 82 | + lines.append(dataLine) |
| 83 | + return lines.joined(separator: "\n") |
| 84 | + } |
| 85 | + |
| 86 | + private static func rowToInsert(columns: [ColumnInfo], row: [String?], tableName: String) -> String { |
| 87 | + let cols = columns.map { "\"\($0.name)\"" }.joined(separator: ", ") |
| 88 | + let vals = columns.indices.map { i in |
| 89 | + let value = i < row.count ? row[i] : nil |
| 90 | + guard let value else { return "NULL" } |
| 91 | + return "'\(value.replacingOccurrences(of: "'", with: "''"))'" |
| 92 | + }.joined(separator: ", ") |
| 93 | + return "INSERT INTO \"\(tableName)\" (\(cols)) VALUES (\(vals));" |
| 94 | + } |
| 95 | + |
| 96 | + private static func escapeCsvField(_ field: String) -> String { |
| 97 | + if field.contains(",") || field.contains("\"") || field.contains("\n") || field.contains("\r") { |
| 98 | + return "\"" + field.replacingOccurrences(of: "\"", with: "\"\"") + "\"" |
| 99 | + } |
| 100 | + return field |
| 101 | + } |
| 102 | + |
| 103 | + private static func escapeJsonString(_ str: String) -> String { |
| 104 | + str.replacingOccurrences(of: "\\", with: "\\\\") |
| 105 | + .replacingOccurrences(of: "\"", with: "\\\"") |
| 106 | + .replacingOccurrences(of: "\n", with: "\\n") |
| 107 | + .replacingOccurrences(of: "\r", with: "\\r") |
| 108 | + .replacingOccurrences(of: "\t", with: "\\t") |
| 109 | + } |
| 110 | +} |
0 commit comments