csv2excel/main.go
Oliver Jakoubek 26b874674f feat: initialer Commit des csv2excel CLI-Tools
Go-CLI-Tool zum Konvertieren von CSV-Dateien in Excel (.xlsx),
mit Mage-Build-System und Architektur-Dokumentation.
2026-03-05 10:14:52 +01:00

127 lines
2.6 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)
}
fmt.Println(sep)
fmt.Println(enc)
fmt.Println(out)
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 {
fmt.Fprintf(os.Stderr, "Fehler beim Speichern: %v\n", err)
os.Exit(1)
}
fmt.Printf("✅ Gespeichert: %s\n", *out)
}
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 ','
}