Fix bug where -o flag value was read as pointer instead of string value, causing memory addresses to be printed and output path to resolve to ".xlsx".
129 lines
2.7 KiB
Go
129 lines
2.7 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/csv"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/xuri/excelize/v2"
|
|
"golang.org/x/text/encoding/charmap"
|
|
"golang.org/x/text/transform"
|
|
)
|
|
|
|
func main() {
|
|
sep := flag.String("sep", "auto", "Trennzeichen: auto, ',', ';', '\\t'")
|
|
enc := flag.String("enc", "utf8", "Encoding: utf8, windows1252")
|
|
out := flag.String("o", "output.xlsx", "Ausgabedatei")
|
|
flag.Parse()
|
|
|
|
files := flag.Args()
|
|
if len(files) == 0 {
|
|
fmt.Fprintln(os.Stderr, "Verwendung: csv2xlsx [flags] datei1.csv datei2.csv ...")
|
|
os.Exit(1)
|
|
}
|
|
|
|
if err := run(files, *out, *sep, *enc); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func run(files []string, out, sep, enc string) error {
|
|
xlsx := excelize.NewFile()
|
|
firstSheet := true
|
|
|
|
for _, path := range files {
|
|
|
|
sheetName := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
|
|
// Excelize: max 31 Zeichen, keine Sonderzeichen
|
|
if len(sheetName) > 31 {
|
|
sheetName = sheetName[:31]
|
|
}
|
|
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Fehler beim Öffnen %s: %v\n", path, err)
|
|
continue
|
|
}
|
|
defer f.Close()
|
|
|
|
var reader io.Reader = f
|
|
if enc == "windows1252" {
|
|
reader = transform.NewReader(f, charmap.Windows1252.NewDecoder())
|
|
}
|
|
|
|
delimiter := detectDelimiter(path, sep)
|
|
|
|
csvReader := csv.NewReader(reader)
|
|
csvReader.Comma = delimiter
|
|
csvReader.LazyQuotes = true // tolerant bei kaputten CSV-Dateien
|
|
|
|
if firstSheet {
|
|
xlsx.SetSheetName("Sheet1", sheetName)
|
|
firstSheet = false
|
|
} else {
|
|
xlsx.NewSheet(sheetName)
|
|
}
|
|
|
|
row := 1
|
|
for {
|
|
record, err := csvReader.Read()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Warnung Zeile %d in %s: %v\n", row, path, err)
|
|
continue
|
|
}
|
|
for col, val := range record {
|
|
cell, _ := excelize.CoordinatesToCellName(col+1, row)
|
|
xlsx.SetCellValue(sheetName, cell, val)
|
|
}
|
|
row++
|
|
}
|
|
fmt.Printf("✓ %s → Reiter \"%s\" (%d Zeilen)\n", path, sheetName, row-1)
|
|
}
|
|
|
|
if err := xlsx.SaveAs(out); err != nil {
|
|
return fmt.Errorf("Fehler beim Speichern: %w", err)
|
|
}
|
|
fmt.Printf("✅ Gespeichert: %s\n", out)
|
|
return nil
|
|
}
|
|
|
|
func detectDelimiter(path, hint string) rune {
|
|
switch hint {
|
|
case ";":
|
|
return ';'
|
|
case "\t":
|
|
return '\t'
|
|
case ",":
|
|
return ','
|
|
}
|
|
// auto-detect: erste Zeile lesen und zählen
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return ';'
|
|
}
|
|
defer f.Close()
|
|
buf := make([]byte, 4096)
|
|
n, _ := f.Read(buf)
|
|
line := string(buf[:n])
|
|
if idx := strings.Index(line, "\n"); idx > 0 {
|
|
line = line[:idx]
|
|
}
|
|
sc := strings.Count(line, ";")
|
|
cc := strings.Count(line, ",")
|
|
tc := strings.Count(line, "\t")
|
|
if tc >= sc && tc >= cc {
|
|
return '\t'
|
|
}
|
|
if sc >= cc {
|
|
return ';'
|
|
}
|
|
return ','
|
|
}
|