From 99692ff0fc2c340e7e1de2497f54779d6812f3a6 Mon Sep 17 00:00:00 2001 From: mwildgoose <62606885+mwildgoose@users.noreply.github.com> Date: Sat, 16 Jul 2022 13:41:32 +0100 Subject: [PATCH] Fix CSV Export Format (#73) * Make CSV export RFC 4180 compliant * Correct CSV MIME type * Remove disabled columns from exports * Auto format so that indentation is consistent * Use string interpolation for string formatting Apply suggestion from @h1dden-da3m0n to use string interpolation to add double quotes to escaped text. --- .../Api/Common/ReportBuilderBase.cs | 13 +- .../Api/Data/ReportExport.cs | 401 +++++++++--------- Jellyfin.Plugin.Reports/Api/ReportsService.cs | 4 +- 3 files changed, 209 insertions(+), 209 deletions(-) diff --git a/Jellyfin.Plugin.Reports/Api/Common/ReportBuilderBase.cs b/Jellyfin.Plugin.Reports/Api/Common/ReportBuilderBase.cs index a1d99d1..8f83802 100644 --- a/Jellyfin.Plugin.Reports/Api/Common/ReportBuilderBase.cs +++ b/Jellyfin.Plugin.Reports/Api/Common/ReportBuilderBase.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using System; using System.Collections.Generic; @@ -211,16 +211,11 @@ namespace Jellyfin.Plugin.Reports.Api.Common foreach (ReportHeader header in options.Select(x => x.Header)) { - if (this.DisplayTypeVisible(header.DisplayType, displayType)) + if ((!DisplayTypeVisible(header.DisplayType, displayType)) || (!headersMetadataFiltered.Contains(header.FieldName) && header.DisplayType != ReportDisplayType.Export) + || (!headersMetadataFiltered.Contains(HeaderMetadata.Status) && header.DisplayType == ReportDisplayType.Export)) { - - if (!headersMetadataFiltered.Contains(header.FieldName) && displayType != ReportDisplayType.Export) - { - header.DisplayType = ReportDisplayType.None; - } - } - else header.DisplayType = ReportDisplayType.None; + } } } diff --git a/Jellyfin.Plugin.Reports/Api/Data/ReportExport.cs b/Jellyfin.Plugin.Reports/Api/Data/ReportExport.cs index 4d50039..a015fce 100644 --- a/Jellyfin.Plugin.Reports/Api/Data/ReportExport.cs +++ b/Jellyfin.Plugin.Reports/Api/Data/ReportExport.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using System.Collections.Generic; using System.Linq; @@ -9,229 +9,234 @@ namespace Jellyfin.Plugin.Reports.Api.Data { /// A report export. public static class ReportExport - { - /// Export to CSV. - /// The report result. - /// A string. - public static string ExportToCsv(ReportResult reportResult) - { - static void AppendRows(StringBuilder builder, List rows) - { - foreach (ReportRow row in rows) - { - builder.AppendJoin(';', row.Columns.Select(s => s.Name.Replace(',', ' '))).AppendLine(); - } - } + { + /// Export to CSV. + /// The report result. + /// A string. + public static string ExportToCsv(ReportResult reportResult) + { + static string EscapeText(string text) + { + string escapedText = text.Replace("\"", "\"\"", System.StringComparison.Ordinal); + return text.IndexOfAny(new char[4] { '"', ',', '\n', '\r' }) == -1 ? escapedText : $"\"{escapedText}\""; + } + static void AppendRows(StringBuilder builder, List rows) + { + foreach (ReportRow row in rows) + { + builder.AppendJoin(',', row.Columns.Select(s => EscapeText(s.Name))).AppendLine(); + } + } - StringBuilder returnValue = new StringBuilder(); - returnValue.AppendJoin(';', reportResult.Headers.Select(s => s.Name.Replace(',', ' '))).AppendLine(); + StringBuilder returnValue = new StringBuilder(); + returnValue.AppendJoin(',', reportResult.Headers.Select(s => EscapeText(s.Name))).AppendLine(); - if (reportResult.IsGrouped) - { - foreach (ReportGroup group in reportResult.Groups) - { - AppendRows(returnValue, group.Rows); - } - } - else - { - AppendRows(returnValue, reportResult.Rows); - } + if (reportResult.IsGrouped) + { + foreach (ReportGroup group in reportResult.Groups) + { + AppendRows(returnValue, group.Rows); + } + } + else + { + AppendRows(returnValue, reportResult.Rows); + } - return returnValue.ToString(); - } + return returnValue.ToString(); + } - /// Export to excel. - /// The report result. - /// A string. - public static string ExportToExcel(ReportResult reportResult) - { - const string Style = @""; + table.gridtable th { + border-width: 0.1pt; + padding: 8px; + border-style: solid; + border-color: #666666; + background-color: #dedede; + } + table.gridtable tr { + background-color: #ffffff; + } + table.gridtable td { + border-width: 0.1pt; + padding: 8px; + border-style: solid; + border-color: #666666; + background-color: #ffffff; + } + "; - string Html = @" - - - - - Jellyfin Reports Export"; - Html += "\n" + Style + "\n"; - Html += "\n"; - Html += "\n"; + string Html = @" + + + + + Jellyfin Reports Export"; + Html += "\n" + Style + "\n"; + Html += "\n"; + Html += "\n"; - StringBuilder returnValue = new StringBuilder(); - returnValue.AppendLine(""); - returnValue.AppendLine(""); - foreach (var x in reportResult.Headers) - { - returnValue.Append(""); - } + StringBuilder returnValue = new StringBuilder(); + returnValue.AppendLine("
") - .Append(x.Name) - .AppendLine("
"); + returnValue.AppendLine(""); + foreach (var x in reportResult.Headers) + { + returnValue.Append(""); + } - returnValue.AppendLine(""); + returnValue.AppendLine(""); - if (reportResult.IsGrouped) - { - foreach (ReportGroup group in reportResult.Groups) - { - returnValue.AppendLine(""); - returnValue.Append(""); - returnValue.AppendLine(""); - ExportToExcelRows(returnValue, group.Rows); - returnValue.AppendLine(""); - returnValue.Append(""); - returnValue.AppendLine(""); - } - } - else - { - ExportToExcelRows(returnValue, reportResult.Rows); - } + if (reportResult.IsGrouped) + { + foreach (ReportGroup group in reportResult.Groups) + { + returnValue.AppendLine(""); + returnValue.Append(""); + returnValue.AppendLine(""); + ExportToExcelRows(returnValue, group.Rows); + returnValue.AppendLine(""); + returnValue.Append(""); + returnValue.AppendLine(""); + } + } + else + { + ExportToExcelRows(returnValue, reportResult.Rows); + } - returnValue.AppendLine("
") + .Append(x.Name) + .AppendLine("
") - .Append(string.IsNullOrEmpty(group.Name) ? " " : group.Name) - .AppendLine("
" + " " + "
") + .Append(string.IsNullOrEmpty(group.Name) ? " " : group.Name) + .AppendLine("
" + " " + "
"); + returnValue.AppendLine(""); - Html += returnValue.ToString(); - Html += ""; - Html += ""; - return Html; - } + Html += returnValue.ToString(); + Html += ""; + Html += ""; + return Html; + } - private static void ExportToExcelRows( - StringBuilder returnValue, - List rows) - { - foreach (var row in rows) - { - returnValue.AppendLine(""); - foreach (var x in row.Columns) - { - returnValue.Append("") - .Append(x.Name) - .AppendLine(""); - } + private static void ExportToExcelRows( + StringBuilder returnValue, + List rows) + { + foreach (var row in rows) + { + returnValue.AppendLine(""); + foreach (var x in row.Columns) + { + returnValue.Append("") + .Append(x.Name) + .AppendLine(""); + } - returnValue.AppendLine(""); - } - } - } + returnValue.AppendLine(""); + } + } + } } diff --git a/Jellyfin.Plugin.Reports/Api/ReportsService.cs b/Jellyfin.Plugin.Reports/Api/ReportsService.cs index 4b467d6..00b96cf 100644 --- a/Jellyfin.Plugin.Reports/Api/ReportsService.cs +++ b/Jellyfin.Plugin.Reports/Api/ReportsService.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using System; using System.Collections.Generic; @@ -107,7 +107,7 @@ namespace Jellyfin.Plugin.Reports.Api ReportViewType reportViewType = ReportHelper.GetReportViewType(request.ReportView); var headers = new Dictionary(); string fileExtension = "csv"; - string contentType = "text/plain;charset='utf-8'"; + string contentType = "text/csv;charset='utf-8'"; switch (request.ExportType) {