feat(oo): sheet-aware spreadsheet export (docs csv/json)
- 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
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user