2026-03-05 10:14:52 +01:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 10:56:18 +01:00
|
|
|
if err := run(files, *out, *sep, *enc); err != nil {
|
|
|
|
|
fmt.Fprintln(os.Stderr, err)
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-05 10:14:52 +01:00
|
|
|
|
2026-03-05 10:56:18 +01:00
|
|
|
func run(files []string, out, sep, enc string) error {
|
2026-03-05 10:14:52 +01:00
|
|
|
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
|
2026-03-05 10:56:18 +01:00
|
|
|
if enc == "windows1252" {
|
2026-03-05 10:14:52 +01:00
|
|
|
reader = transform.NewReader(f, charmap.Windows1252.NewDecoder())
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 10:56:18 +01:00
|
|
|
delimiter := detectDelimiter(path, sep)
|
2026-03-05 10:14:52 +01:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 10:56:18 +01:00
|
|
|
if err := xlsx.SaveAs(out); err != nil {
|
|
|
|
|
return fmt.Errorf("Fehler beim Speichern: %w", err)
|
2026-03-05 10:14:52 +01:00
|
|
|
}
|
2026-03-05 10:56:18 +01:00
|
|
|
fmt.Printf("✅ Gespeichert: %s\n", out)
|
|
|
|
|
return nil
|
2026-03-05 10:14:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 ','
|
|
|
|
|
}
|