- lib: WorkbookSheetNames/CSV/JSON (excelize) — sheet selection + JSON rows - oo docs csv SRC... [--sheet N] [--delimiter ,|;|||tab] [--out] - oo docs json SRC... [--sheet N] [--out] - SRC = OO file id or local path; --sheet is 1-based (default first) - DS csv output is first-sheet-only (verified) -> local reader for selection/JSON - test: TestWorkbookSheetExport
124 lines
3.2 KiB
Go
124 lines
3.2 KiB
Go
package onlyoffice
|
|
|
|
// Spreadsheet (XLS/XLSX/ODS) sheet-aware export to CSV / JSON.
|
|
//
|
|
// The OnlyOffice DocumentServer converter can emit CSV, but only for the first
|
|
// worksheet and it ignores any sheet selector (verified against a live DS).
|
|
// So sheet selection and JSON use a local reader (excelize). This is data
|
|
// extraction, not a document-format conversion pipeline.
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/csv"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/xuri/excelize/v2"
|
|
)
|
|
|
|
// WorkbookSheetNames returns worksheet names in workbook order.
|
|
func WorkbookSheetNames(data []byte) ([]string, error) {
|
|
f, err := excelize.OpenReader(bytes.NewReader(data))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open workbook: %w", err)
|
|
}
|
|
defer f.Close()
|
|
return f.GetSheetList(), nil
|
|
}
|
|
|
|
// workbookSheet resolves a 0-based sheet index to its name. A negative index
|
|
// selects the first sheet; an out-of-range index is an error.
|
|
func workbookSheet(f *excelize.File, index int) (string, error) {
|
|
names := f.GetSheetList()
|
|
if len(names) == 0 {
|
|
return "", fmt.Errorf("workbook has no sheets")
|
|
}
|
|
if index < 0 {
|
|
index = 0
|
|
}
|
|
if index >= len(names) {
|
|
return "", fmt.Errorf("sheet index %d out of range (0..%d)", index, len(names)-1)
|
|
}
|
|
return names[index], nil
|
|
}
|
|
|
|
// WorkbookSheetCSV renders one worksheet as CSV. sheetIndex is 0-based
|
|
// (negative = first). delimiter 0 keeps the default comma. header, when true,
|
|
// is kept as the first CSV row (it is always kept; the flag only affects JSON).
|
|
func WorkbookSheetCSV(data []byte, sheetIndex int, delimiter rune) (string, error) {
|
|
f, err := excelize.OpenReader(bytes.NewReader(data))
|
|
if err != nil {
|
|
return "", fmt.Errorf("open workbook: %w", err)
|
|
}
|
|
defer f.Close()
|
|
name, err := workbookSheet(f, sheetIndex)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
rows, err := f.GetRows(name)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var b strings.Builder
|
|
w := csv.NewWriter(&b)
|
|
if delimiter != 0 {
|
|
w.Comma = delimiter
|
|
}
|
|
for _, row := range rows {
|
|
_ = w.Write(row)
|
|
}
|
|
w.Flush()
|
|
return b.String(), w.Error()
|
|
}
|
|
|
|
// WorkbookSheetJSON renders one worksheet as a list of row objects, using the
|
|
// first row as the field names (blank headers become col1, col2, …). sheetIndex
|
|
// is 0-based (negative = first).
|
|
func WorkbookSheetJSON(data []byte, sheetIndex int) ([]map[string]any, error) {
|
|
f, err := excelize.OpenReader(bytes.NewReader(data))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open workbook: %w", err)
|
|
}
|
|
defer f.Close()
|
|
name, err := workbookSheet(f, sheetIndex)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rows, err := f.GetRows(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(rows) == 0 {
|
|
return nil, nil
|
|
}
|
|
header := rows[0]
|
|
out := make([]map[string]any, 0, len(rows)-1)
|
|
for _, row := range rows[1:] {
|
|
m := make(map[string]any, len(header))
|
|
for i, h := range header {
|
|
key := strings.TrimSpace(h)
|
|
if key == "" {
|
|
key = fmt.Sprintf("col%d", i+1)
|
|
}
|
|
if i < len(row) {
|
|
m[key] = row[i]
|
|
} else {
|
|
m[key] = ""
|
|
}
|
|
}
|
|
out = append(out, m)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// WorkbookSheetCSVTo is WorkbookSheetCSV writing into w.
|
|
func WorkbookSheetCSVTo(data []byte, sheetIndex int, delimiter rune, w io.Writer) error {
|
|
s, err := WorkbookSheetCSV(data, sheetIndex, delimiter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = io.WriteString(w, s)
|
|
return err
|
|
}
|