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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+