From 6cb6e209ebb4549a0e89cd9471449cb6a765307c Mon Sep 17 00:00:00 2001 From: Romein van Buren Date: Sun, 25 Jun 2023 20:29:28 +0200 Subject: [PATCH 1/4] Experimental Excel export feature (WIP) --- .../collection/dialogs/export.svelte | 7 +- internal/app/collection_find_export.go | 289 +++++++++++++++--- .../app/collection_find_export_excel/app.xml | 31 ++ .../contenttypes.xml | 12 + .../app/collection_find_export_excel/core.xml | 18 ++ .../app/collection_find_export_excel/rels.xml | 7 + .../collection_find_export_excel/styles.xml | 76 +++++ .../collection_find_export_excel/theme.xml | 269 ++++++++++++++++ 8 files changed, 660 insertions(+), 49 deletions(-) create mode 100644 internal/app/collection_find_export_excel/app.xml create mode 100644 internal/app/collection_find_export_excel/contenttypes.xml create mode 100644 internal/app/collection_find_export_excel/core.xml create mode 100644 internal/app/collection_find_export_excel/rels.xml create mode 100644 internal/app/collection_find_export_excel/styles.xml create mode 100644 internal/app/collection_find_export_excel/theme.xml diff --git a/frontend/src/organisms/connection/collection/dialogs/export.svelte b/frontend/src/organisms/connection/collection/dialogs/export.svelte index c18e301..b314f7b 100644 --- a/frontend/src/organisms/connection/collection/dialogs/export.svelte +++ b/frontend/src/organisms/connection/collection/dialogs/export.svelte @@ -30,9 +30,10 @@ diff --git a/internal/app/collection_find_export.go b/internal/app/collection_find_export.go index 078a355..bb7064b 100644 --- a/internal/app/collection_find_export.go +++ b/internal/app/collection_find_export.go @@ -1,10 +1,14 @@ package app import ( + "archive/zip" + _ "embed" "encoding/csv" "encoding/json" "fmt" + "io" "os" + "strings" "github.com/wailsapp/wails/v2/pkg/runtime" "go.mongodb.org/mongo-driver/bson" @@ -23,6 +27,24 @@ const ( ExportFormatJsonArray ExportFormat = "jsonarray" ExportFormatNdJson ExportFormat = "ndjson" ExportFormatCsv ExportFormat = "csv" + ExportFormatExcel ExportFormat = "excel" +) + +var ( + //go:embed collection_find_export_excel/app.xml + excelAppXml string + //go:embed collection_find_export_excel/core.xml + excelCoreXml string + //go:embed collection_find_export_excel/rels.xml + excelRelsXml string + //go:embed collection_find_export_excel/styles.xml + excelStylesXml string + //go:embed collection_find_export_excel/theme.xml + excelThemeXml string + //go:embed collection_find_export_excel/contenttypes.xml + excelContentTypesXml string + + alphabet = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ") ) type ExportSettings struct { @@ -39,6 +61,29 @@ func getptr[T any](v T) *T { return &v } +func excelColIndex(idx int) string { + str := make([]rune, 0) + + for idx > 0 { + rem := idx % 26 + if rem == 0 { + str = append(str, 'Z') + idx = (idx / 26) - 1 + } else { + + str = append(str, alphabet[rem-1]) + idx = (idx / 26) + } + } + + // Reverse string + for i, j := 0, len(str)-1; i < j; i, j = i+1, j-1 { + str[i], str[j] = str[j], str[i] + } + + return string(str) +} + func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bool { runtime.LogInfof(a.ctx, "Export started for %s/%s/%s. Settings: %s", hostKey, dbKey, collKey, settingsJson) @@ -90,18 +135,27 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo DisplayName: "CSV files (*.csv)", Pattern: "*.csv", } + case ExportFormatJsonArray: defaultFilename = "export.json" fileFilter = runtime.FileFilter{ DisplayName: "JSON files (*.json)", Pattern: "*.json", } + case ExportFormatNdJson: defaultFilename = "export.ndjson" fileFilter = runtime.FileFilter{ DisplayName: "Newline delimited JSON files (*.ndjson)", Pattern: "*.ndjson", } + + case ExportFormatExcel: + defaultFilename = "export.xlsx" + fileFilter = runtime.FileFilter{ + DisplayName: "Microsoft Excel Workbook (*.xlsx)", + Pattern: "*.xlsx", + } } settings.OutFile, err = runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{ @@ -123,12 +177,11 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo if settings.OutFile == "" { runtime.LogDebug(a.ctx, "Export: no destination specified") runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ - Message: "Please specify ab export destination.", + Message: "Please specify an export destination.", Type: runtime.ErrorDialog, }) return false } - if _, err := os.Stat(settings.OutFile); err == nil { runtime.LogDebugf(a.ctx, "Export: destination %s already exists", settings.OutFile) runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ @@ -201,9 +254,14 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo } defer file.Close() - var csvWriter *csv.Writer - var csvColumnKeys []string var index uint = 0 + var columnKeys []string + + var csvWriter *csv.Writer + + var excelZipWriter *zip.Writer + var excelSheetWriter io.Writer + var excelStrings = make([]string, 0) switch settings.Format { case ExportFormatJsonArray: @@ -211,11 +269,78 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo case ExportFormatCsv: csvWriter = csv.NewWriter(file) + + case ExportFormatExcel: + excelZipWriter = zip.NewWriter(file) + + files := map[string]string{ + "docProps/app.xml": excelAppXml, + "docProps/core.xml": strings.Replace(excelCoreXml, "{TITLE}", fmt.Sprintf("%s.%s", dbKey, collKey), 1), + "xl/theme/theme1.xml": excelThemeXml, + "xl/rels/workbook.xml.rels": excelRelsXml, + "xl/styles.xml": excelStylesXml, + "[Content-Types].xml": excelContentTypesXml, + } + + for fname, body := range files { + f, err := excelZipWriter.Create(fname) + if err != nil { + runtime.LogErrorf(a.ctx, "Export: Excel zip.Create error: %s", err.Error()) + runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ + Title: "ZIP error!", + Message: err.Error(), + Type: runtime.ErrorDialog, + }) + return false + } + + _, err = f.Write([]byte(body)) + if err != nil { + runtime.LogErrorf(a.ctx, "Export: Excel zip.Create.Write error: %s", err.Error()) + runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ + Title: "ZIP error!", + Message: err.Error(), + Type: runtime.ErrorDialog, + }) + return false + } + } + + excelZipWriter.Create("_rels/") + + excelSheetWriter, err = excelZipWriter.Create("xl/worksheets/sheet1.xml") + if err != nil { + runtime.LogErrorf(a.ctx, "Export: Excel ZIP error creating worksheet: %s", err.Error()) + runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ + Title: "ZIP error!", + Message: err.Error(), + Type: runtime.ErrorDialog, + }) + return false + } + + excelSheetWriter.Write([]byte(` + + + + + + + + + `)) } for cur.Next(ctx) { - switch settings.Format { - case ExportFormatCsv: + if settings.ViewKey == "list" && columnKeys == nil { + columnKeys = make([]string, 0) els, err := cur.Current.Elements() if err != nil { runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ @@ -225,48 +350,59 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo }) } + for _, el := range els { + if el.Key() == "" { + continue + } + + switch el.Value().Type { + case bsontype.Boolean, + bsontype.Decimal128, + bsontype.Double, + bsontype.Int32, + bsontype.Int64, + bsontype.Null, + bsontype.ObjectID, + bsontype.Regex, + bsontype.String, + bsontype.Symbol, + bsontype.Timestamp, + bsontype.Undefined: + columnKeys = append(columnKeys, el.Key()) + } + } + + runtime.LogDebugf(a.ctx, "Export column keys: %v", columnKeys) + + switch settings.Format { + case ExportFormatCsv: + if err := csvWriter.Write(columnKeys); err != nil { + runtime.LogInfof(a.ctx, "Unable to write item %d to CSV while exporting: %s", index, err.Error()) + runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ + Title: fmt.Sprintf("Unable to write item %d to CSV", index), + Message: err.Error(), + Type: runtime.ErrorDialog, + }) + } + + case ExportFormatExcel: + excelSheetWriter.Write([]byte(fmt.Sprintf(``, len(columnKeys)))) + for idx, key := range columnKeys { + // excelStringsWriter.Write([]byte(fmt.Sprintf("%s", key))) + excelStrings = append(excelStrings, key) + excelSheetWriter.Write([]byte(fmt.Sprintf(`%d`, excelColIndex(idx+1), len(excelStrings)))) + } + excelSheetWriter.Write([]byte("")) + } + } + + switch settings.Format { + case ExportFormatCsv: csvItem := make([]string, 0) switch settings.ViewKey { case "list": - if csvColumnKeys == nil { - csvColumnKeys = make([]string, 0) - - for _, el := range els { - if el.Key() == "" { - continue - } - - switch el.Value().Type { - case bsontype.Boolean, - bsontype.Decimal128, - bsontype.Double, - bsontype.Int32, - bsontype.Int64, - bsontype.Null, - bsontype.ObjectID, - bsontype.Regex, - bsontype.String, - bsontype.Symbol, - bsontype.Timestamp, - bsontype.Undefined: - csvColumnKeys = append(csvColumnKeys, el.Key()) - } - } - - runtime.LogDebugf(a.ctx, "Export csvColumnKeys: %v", csvColumnKeys) - - if err := csvWriter.Write(csvColumnKeys); err != nil { - runtime.LogInfof(a.ctx, "Unable to write item %d to CSV while exporting: %s", index, err.Error()) - runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ - Title: fmt.Sprintf("Unable to write item %d to CSV", index), - Message: err.Error(), - Type: runtime.ErrorDialog, - }) - } - } - - for _, k := range csvColumnKeys { + for _, k := range columnKeys { r, err := cur.Current.LookupErr(k) if err != nil { csvItem = append(csvItem, "") @@ -322,14 +458,75 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo if settings.Format == ExportFormatNdJson { file.WriteString("\n") } + + case ExportFormatExcel: + excelRow := make([]string, 0) + + for _, k := range columnKeys { + r, err := cur.Current.LookupErr(k) + if err != nil { + excelRow = append(excelRow, "") + continue + } + + var v any + if err := r.Unmarshal(&v); err != nil { + runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ + Title: fmt.Sprintf("Unable to unmarshal field %s", k), + Message: err.Error(), + Type: runtime.ErrorDialog, + }) + excelRow = append(excelRow, "") + continue + } + + excelRow = append(excelRow, fmt.Sprintf("%v", v)) + } + + excelSheetWriter.Write([]byte(fmt.Sprintf(``, index+2, len(columnKeys)))) + for idx, val := range excelRow { + // excelStringsWriter.Write([]byte(fmt.Sprintf("%s", val))) + excelStrings = append(excelStrings, val) + excelSheetWriter.Write([]byte(fmt.Sprintf(`%d`, excelColIndex(idx+1), index+2, len(excelStrings)))) + } + excelSheetWriter.Write([]byte("")) } index++ - } - if settings.Format == ExportFormatJsonArray { + switch settings.Format { + case ExportFormatJsonArray: file.WriteString("]\n") + + case ExportFormatExcel: + excelSheetWriter.Write([]byte(``)) + + excelStringsWriter, err := excelZipWriter.Create("xl/sharedStrings.xml") + if err != nil { + runtime.LogErrorf(a.ctx, "Export: Excel ZIP error creating shared strings: %s", err.Error()) + runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ + Title: "ZIP error!", + Message: err.Error(), + Type: runtime.ErrorDialog, + }) + return false + } + + excelStringsWriter.Write([]byte(fmt.Sprintf(`%s`, len(excelStrings), len(excelStrings), "\r\n"))) + for _, str := range excelStrings { + excelStringsWriter.Write([]byte(fmt.Sprintf("%s\r\n", str))) + } + excelStringsWriter.Write([]byte("")) + + if err := excelZipWriter.Close(); err != nil { + runtime.LogErrorf(a.ctx, "Export: Excel ZIP error while closing: %s", err.Error()) + runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ + Title: "ZIP error!", + Message: err.Error(), + Type: runtime.ErrorDialog, + }) + } } a.ui.Reveal(settings.OutFile) diff --git a/internal/app/collection_find_export_excel/app.xml b/internal/app/collection_find_export_excel/app.xml new file mode 100644 index 0000000..52ba16f --- /dev/null +++ b/internal/app/collection_find_export_excel/app.xml @@ -0,0 +1,31 @@ + + + RolensExport + 0 + false + + + + Sheets + + + 1 + + + + + + RolensExport + + + + + false + false + + false + 16.0300 + diff --git a/internal/app/collection_find_export_excel/contenttypes.xml b/internal/app/collection_find_export_excel/contenttypes.xml new file mode 100644 index 0000000..2312f21 --- /dev/null +++ b/internal/app/collection_find_export_excel/contenttypes.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/internal/app/collection_find_export_excel/core.xml b/internal/app/collection_find_export_excel/core.xml new file mode 100644 index 0000000..bb61633 --- /dev/null +++ b/internal/app/collection_find_export_excel/core.xml @@ -0,0 +1,18 @@ + + + {TITLE} + + Rolens + + + + 2020-01-01T00:00:00Z + 2020-01-01T00:00:00Z + + diff --git a/internal/app/collection_find_export_excel/rels.xml b/internal/app/collection_find_export_excel/rels.xml new file mode 100644 index 0000000..310c413 --- /dev/null +++ b/internal/app/collection_find_export_excel/rels.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/internal/app/collection_find_export_excel/styles.xml b/internal/app/collection_find_export_excel/styles.xml new file mode 100644 index 0000000..9df2015 --- /dev/null +++ b/internal/app/collection_find_export_excel/styles.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/app/collection_find_export_excel/theme.xml b/internal/app/collection_find_export_excel/theme.xml new file mode 100644 index 0000000..264e5d5 --- /dev/null +++ b/internal/app/collection_find_export_excel/theme.xml @@ -0,0 +1,269 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 3055f8173e4944b98920fb44a7d72879e0c87e70 Mon Sep 17 00:00:00 2001 From: Romein van Buren Date: Sun, 25 Jun 2023 22:00:53 +0200 Subject: [PATCH 2/4] Fixed export to Excel feature, it works now --- internal/app/collection_find_export.go | 95 ++++++++---------- .../contenttypes.xml | 23 ++++- .../collection_find_export_excel/dotrels.xml | 6 ++ .../collection_find_export_excel/metadata.xml | 20 ++++ .../template.xlsx | Bin 0 -> 9142 bytes .../collection_find_export_excel/workbook.xml | 10 ++ 6 files changed, 96 insertions(+), 58 deletions(-) create mode 100644 internal/app/collection_find_export_excel/dotrels.xml create mode 100644 internal/app/collection_find_export_excel/metadata.xml create mode 100644 internal/app/collection_find_export_excel/template.xlsx create mode 100644 internal/app/collection_find_export_excel/workbook.xml diff --git a/internal/app/collection_find_export.go b/internal/app/collection_find_export.go index bb7064b..6fca235 100644 --- a/internal/app/collection_find_export.go +++ b/internal/app/collection_find_export.go @@ -6,7 +6,6 @@ import ( "encoding/csv" "encoding/json" "fmt" - "io" "os" "strings" @@ -43,6 +42,12 @@ var ( excelThemeXml string //go:embed collection_find_export_excel/contenttypes.xml excelContentTypesXml string + //go:embed collection_find_export_excel/metadata.xml + excelMetadataXml string + //go:embed collection_find_export_excel/workbook.xml + excelWorkbookXml string + //go:embed collection_find_export_excel/dotrels.xml + excelDotRelsXml string alphabet = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ") ) @@ -260,8 +265,7 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo var csvWriter *csv.Writer var excelZipWriter *zip.Writer - var excelSheetWriter io.Writer - var excelStrings = make([]string, 0) + var excelSheetWriter strings.Builder switch settings.Format { case ExportFormatJsonArray: @@ -274,12 +278,15 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo excelZipWriter = zip.NewWriter(file) files := map[string]string{ - "docProps/app.xml": excelAppXml, - "docProps/core.xml": strings.Replace(excelCoreXml, "{TITLE}", fmt.Sprintf("%s.%s", dbKey, collKey), 1), - "xl/theme/theme1.xml": excelThemeXml, - "xl/rels/workbook.xml.rels": excelRelsXml, - "xl/styles.xml": excelStylesXml, - "[Content-Types].xml": excelContentTypesXml, + "_rels/.rels": excelDotRelsXml, + "docProps/app.xml": excelAppXml, + "docProps/core.xml": strings.Replace(excelCoreXml, "{TITLE}", fmt.Sprintf("%s.%s", dbKey, collKey), 1), + "xl/_rels/workbook.xml.rels": excelRelsXml, + "xl/theme/theme1.xml": excelThemeXml, + "xl/metadata.xml": excelMetadataXml, + "xl/styles.xml": excelStylesXml, + "xl/workbook.xml": excelWorkbookXml, + "[Content_Types].xml": excelContentTypesXml, } for fname, body := range files { @@ -306,36 +313,7 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo } } - excelZipWriter.Create("_rels/") - - excelSheetWriter, err = excelZipWriter.Create("xl/worksheets/sheet1.xml") - if err != nil { - runtime.LogErrorf(a.ctx, "Export: Excel ZIP error creating worksheet: %s", err.Error()) - runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ - Title: "ZIP error!", - Message: err.Error(), - Type: runtime.ErrorDialog, - }) - return false - } - - excelSheetWriter.Write([]byte(` - - - - - - - - - `)) + excelSheetWriter = strings.Builder{} } for cur.Next(ctx) { @@ -388,9 +366,7 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo case ExportFormatExcel: excelSheetWriter.Write([]byte(fmt.Sprintf(``, len(columnKeys)))) for idx, key := range columnKeys { - // excelStringsWriter.Write([]byte(fmt.Sprintf("%s", key))) - excelStrings = append(excelStrings, key) - excelSheetWriter.Write([]byte(fmt.Sprintf(`%d`, excelColIndex(idx+1), len(excelStrings)))) + excelSheetWriter.Write([]byte(fmt.Sprintf(`%s`, excelColIndex(idx+1), key))) } excelSheetWriter.Write([]byte("")) } @@ -485,9 +461,7 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo excelSheetWriter.Write([]byte(fmt.Sprintf(``, index+2, len(columnKeys)))) for idx, val := range excelRow { - // excelStringsWriter.Write([]byte(fmt.Sprintf("%s", val))) - excelStrings = append(excelStrings, val) - excelSheetWriter.Write([]byte(fmt.Sprintf(`%d`, excelColIndex(idx+1), index+2, len(excelStrings)))) + excelSheetWriter.Write([]byte(fmt.Sprintf(`%s`, excelColIndex(idx+1), index+2, val))) } excelSheetWriter.Write([]byte("")) } @@ -500,11 +474,9 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo file.WriteString("]\n") case ExportFormatExcel: - excelSheetWriter.Write([]byte(``)) - - excelStringsWriter, err := excelZipWriter.Create("xl/sharedStrings.xml") + sw, err := excelZipWriter.Create("xl/worksheets/sheet1.xml") if err != nil { - runtime.LogErrorf(a.ctx, "Export: Excel ZIP error creating shared strings: %s", err.Error()) + runtime.LogErrorf(a.ctx, "Export: Excel ZIP error creating worksheet: %s", err.Error()) runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ Title: "ZIP error!", Message: err.Error(), @@ -513,13 +485,26 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo return false } - excelStringsWriter.Write([]byte(fmt.Sprintf(`%s`, len(excelStrings), len(excelStrings), "\r\n"))) - for _, str := range excelStrings { - excelStringsWriter.Write([]byte(fmt.Sprintf("%s\r\n", str))) - } - excelStringsWriter.Write([]byte("")) + sw.Write([]byte(strings.ReplaceAll(fmt.Sprintf(` + + + + + + + `, excelColIndex(len(columnKeys)), index+1), "\n", "\r\n"))) + sw.Write([]byte(excelSheetWriter.String())) + sw.Write([]byte(``)) - if err := excelZipWriter.Close(); err != nil { + err = excelZipWriter.Close() + if err != nil { runtime.LogErrorf(a.ctx, "Export: Excel ZIP error while closing: %s", err.Error()) runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ Title: "ZIP error!", diff --git a/internal/app/collection_find_export_excel/contenttypes.xml b/internal/app/collection_find_export_excel/contenttypes.xml index 2312f21..8a41e3f 100644 --- a/internal/app/collection_find_export_excel/contenttypes.xml +++ b/internal/app/collection_find_export_excel/contenttypes.xml @@ -1,12 +1,29 @@ - - + + + + + + + + + + + + + + + - + diff --git a/internal/app/collection_find_export_excel/dotrels.xml b/internal/app/collection_find_export_excel/dotrels.xml new file mode 100644 index 0000000..a719722 --- /dev/null +++ b/internal/app/collection_find_export_excel/dotrels.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/internal/app/collection_find_export_excel/metadata.xml b/internal/app/collection_find_export_excel/metadata.xml new file mode 100644 index 0000000..99f5b57 --- /dev/null +++ b/internal/app/collection_find_export_excel/metadata.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/internal/app/collection_find_export_excel/template.xlsx b/internal/app/collection_find_export_excel/template.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..0a4518fa6b1e08316e971a5fb5d1f5f65b522465 GIT binary patch literal 9142 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 literal 0 HcmV?d00001 diff --git a/internal/app/collection_find_export_excel/workbook.xml b/internal/app/collection_find_export_excel/workbook.xml new file mode 100644 index 0000000..6b4c93a --- /dev/null +++ b/internal/app/collection_find_export_excel/workbook.xml @@ -0,0 +1,10 @@ + + + + + + + From e6b00408217ed596c10dc99ee1c1d390642ce69f Mon Sep 17 00:00:00 2001 From: Romein van Buren Date: Mon, 26 Jun 2023 11:16:04 +0200 Subject: [PATCH 3/4] Debug Excel export script --- internal/app/collection_find_export.go | 128 +++++++++++++------------ 1 file changed, 65 insertions(+), 63 deletions(-) diff --git a/internal/app/collection_find_export.go b/internal/app/collection_find_export.go index 6fca235..7e5e8c9 100644 --- a/internal/app/collection_find_export.go +++ b/internal/app/collection_find_export.go @@ -6,6 +6,7 @@ import ( "encoding/csv" "encoding/json" "fmt" + "html" "os" "strings" @@ -32,22 +33,22 @@ const ( var ( //go:embed collection_find_export_excel/app.xml excelAppXml string + //go:embed collection_find_export_excel/contenttypes.xml + excelContentTypesXml string //go:embed collection_find_export_excel/core.xml excelCoreXml string + //go:embed collection_find_export_excel/dotrels.xml + excelDotRelsXml string + //go:embed collection_find_export_excel/metadata.xml + excelMetadataXml string //go:embed collection_find_export_excel/rels.xml excelRelsXml string //go:embed collection_find_export_excel/styles.xml excelStylesXml string //go:embed collection_find_export_excel/theme.xml excelThemeXml string - //go:embed collection_find_export_excel/contenttypes.xml - excelContentTypesXml string - //go:embed collection_find_export_excel/metadata.xml - excelMetadataXml string //go:embed collection_find_export_excel/workbook.xml excelWorkbookXml string - //go:embed collection_find_export_excel/dotrels.xml - excelDotRelsXml string alphabet = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ") ) @@ -94,7 +95,7 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo var settings ExportSettings if err := json.Unmarshal([]byte(settingsJson), &settings); err != nil { - runtime.LogWarningf(a.ctx, "Could not parse export settings: %s", err.Error()) + runtime.LogWarningf(a.ctx, "Export: Could not parse settings: %s", err.Error()) runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ Title: "Couldn't parse export settings!", Message: err.Error(), @@ -122,7 +123,7 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo view, found := views[settings.ViewKey] if !found { - runtime.LogDebugf(a.ctx, "Export: unknown view %s", settings.ViewKey) + runtime.LogWarningf(a.ctx, "Export: unknown view %s", settings.ViewKey) runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ Message: fmt.Sprintf("View %s is not known", settings.ViewKey), Type: runtime.ErrorDialog, @@ -199,7 +200,7 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo var query bson.M if settings.Contents != ExportContentsAll { if err = bson.UnmarshalExtJSON([]byte(settings.QueryJson), true, &query); err != nil { - runtime.LogDebugf(a.ctx, "Invalid find query (exporting): %s", settings.QueryJson) + runtime.LogWarningf(a.ctx, "Export: invalid find query: %s", settings.QueryJson) runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ Title: "Invalid query", Message: err.Error(), @@ -218,7 +219,7 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo projection := bson.M{} if settings.ViewKey != "list" { for _, col := range view.Columns { - projection[col.Key] = "" + projection[col.Key] = 1 } } @@ -238,7 +239,7 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo Projection: projection, }) if err != nil { - runtime.LogInfof(a.ctx, "Export: unable to get cursor while exporting: %s", err.Error()) + runtime.LogWarningf(a.ctx, "Export: unable to get cursor while exporting: %s", err.Error()) runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ Title: "Couldn't get cursor", Message: err.Error(), @@ -249,7 +250,7 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo file, err := os.OpenFile(settings.OutFile, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { - runtime.LogDebugf(a.ctx, "Export: unable to open file %s", settings.OutFile) + runtime.LogInfof(a.ctx, "Export: unable to open file %s", settings.OutFile) runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ Title: "Error opening file", Message: err.Error(), @@ -280,7 +281,7 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo files := map[string]string{ "_rels/.rels": excelDotRelsXml, "docProps/app.xml": excelAppXml, - "docProps/core.xml": strings.Replace(excelCoreXml, "{TITLE}", fmt.Sprintf("%s.%s", dbKey, collKey), 1), + "docProps/core.xml": strings.Replace(excelCoreXml, "{TITLE}", fmt.Sprintf("%s.%s Export", dbKey, collKey), 1), "xl/_rels/workbook.xml.rels": excelRelsXml, "xl/theme/theme1.xml": excelThemeXml, "xl/metadata.xml": excelMetadataXml, @@ -317,36 +318,43 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo } for cur.Next(ctx) { - if settings.ViewKey == "list" && columnKeys == nil { + if columnKeys == nil { columnKeys = make([]string, 0) - els, err := cur.Current.Elements() - if err != nil { - runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ - Title: "BSON is invalid", - Message: err.Error(), - Type: runtime.ErrorDialog, - }) - } - for _, el := range els { - if el.Key() == "" { - continue + if settings.ViewKey == "list" { + els, err := cur.Current.Elements() + if err != nil { + runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ + Title: "BSON is invalid", + Message: err.Error(), + Type: runtime.ErrorDialog, + }) } - switch el.Value().Type { - case bsontype.Boolean, - bsontype.Decimal128, - bsontype.Double, - bsontype.Int32, - bsontype.Int64, - bsontype.Null, - bsontype.ObjectID, - bsontype.Regex, - bsontype.String, - bsontype.Symbol, - bsontype.Timestamp, - bsontype.Undefined: - columnKeys = append(columnKeys, el.Key()) + for _, el := range els { + if el.Key() == "" { + continue + } + + switch el.Value().Type { + case bsontype.Boolean, + bsontype.Decimal128, + bsontype.Double, + bsontype.Int32, + bsontype.Int64, + bsontype.Null, + bsontype.ObjectID, + bsontype.Regex, + bsontype.String, + bsontype.Symbol, + bsontype.Timestamp, + bsontype.Undefined: + columnKeys = append(columnKeys, el.Key()) + } + } + } else { + for _, col := range view.Columns { + columnKeys = append(columnKeys, col.Key) } } @@ -376,31 +384,25 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo case ExportFormatCsv: csvItem := make([]string, 0) - switch settings.ViewKey { - case "list": - for _, k := range columnKeys { - r, err := cur.Current.LookupErr(k) - if err != nil { - csvItem = append(csvItem, "") - continue - } - - var v any - if err := r.Unmarshal(&v); err != nil { - runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ - Title: fmt.Sprintf("Unable to unmarshal field %s", k), - Message: err.Error(), - Type: runtime.ErrorDialog, - }) - csvItem = append(csvItem, "") - continue - } - - csvItem = append(csvItem, fmt.Sprintf("%v", v)) + for _, k := range columnKeys { + r, err := cur.Current.LookupErr(k) + if err != nil { + csvItem = append(csvItem, "") + continue } - default: - // @todo + var v any + if err := r.Unmarshal(&v); err != nil { + runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ + Title: fmt.Sprintf("Unable to unmarshal field %s", k), + Message: err.Error(), + Type: runtime.ErrorDialog, + }) + csvItem = append(csvItem, "") + continue + } + + csvItem = append(csvItem, fmt.Sprintf("%v", v)) } if err := csvWriter.Write(csvItem); err != nil { @@ -456,7 +458,7 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo continue } - excelRow = append(excelRow, fmt.Sprintf("%v", v)) + excelRow = append(excelRow, html.EscapeString(fmt.Sprintf("%v", v))) } excelSheetWriter.Write([]byte(fmt.Sprintf(``, index+2, len(columnKeys)))) From 755b96762d82405bd138f2e8ef69fcc1c49ecd7b Mon Sep 17 00:00:00 2001 From: Romein van Buren Date: Mon, 26 Jun 2023 17:33:19 +0200 Subject: [PATCH 4/4] Disabled Dependabot for a while, generates too many PRs --- .github/{dependabot.yml => dependabot.disable.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{dependabot.yml => dependabot.disable.yml} (100%) diff --git a/.github/dependabot.yml b/.github/dependabot.disable.yml similarity index 100% rename from .github/dependabot.yml rename to .github/dependabot.disable.yml